From 1f1d7b1088fd258a4ce659a3c942290a3c6b3a85 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Thu, 13 Dec 2018 12:09:54 +0530 Subject: Fix semRecordCase --- compiler/semstmts.nim | 7 ------- compiler/semtypes.nim | 30 ++++++++++++++++++++---------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index aec03b492..d81410891 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -95,13 +95,6 @@ proc semWhile(c: PContext, n: PNode; flags: TExprFlags): PNode = elif efInTypeof in flags: result.typ = n[1].typ -proc toCover(c: PContext, t: PType): BiggestInt = - let t2 = skipTypes(t, abstractVarRange-{tyTypeDesc}) - if t2.kind == tyEnum and enumHasHoles(t2): - result = sonsLen(t2.n) - else: - result = lengthOrd(c.config, skipTypes(t, abstractVar-{tyTypeDesc})) - proc semProc(c: PContext, n: PNode): PNode proc semExprBranch(c: PContext, n: PNode; flags: TExprFlags = {}): PNode = diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index f4ff97ba4..cb0402018 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -591,6 +591,13 @@ proc semCaseBranch(c: PContext, t, branch: PNode, branchIndex: int, for i in lastIndex.succ..(sonsLen(branch) - 2): checkForOverlap(c, t, i, branchIndex) +proc toCover(c: PContext, t: PType): BiggestInt = + let t2 = skipTypes(t, abstractVarRange-{tyTypeDesc}) + if t2.kind == tyEnum and enumHasHoles(t2): + result = sonsLen(t2.n) + else: + result = lengthOrd(c.config, skipTypes(t, abstractVar-{tyTypeDesc})) + proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int, father: PNode, rectype: PType, hasCaseFields = false) proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int, @@ -603,15 +610,16 @@ proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int, return incl(a.sons[0].sym.flags, sfDiscriminant) var covered: BiggestInt = 0 + var chckCovered = false var typ = skipTypes(a.sons[0].typ, abstractVar-{tyTypeDesc}) - if not isOrdinalType(typ): - localError(c.config, n.info, "selector must be of an ordinal type") - elif firstOrd(c.config, typ) != 0: - localError(c.config, n.info, "low(" & $a.sons[0].sym.name.s & - ") must be 0 for discriminant") - elif lengthOrd(c.config, typ) > 0x00007FFF: - localError(c.config, n.info, "len($1) must be less than 32768" % a.sons[0].sym.name.s) - var chckCovered = true + case typ.kind + of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32, tyBool: + chckCovered = true + of tyFloat..tyFloat128, tyString, tyError: + discard + else: + localError(c.config, n.info, "selector must be of an ordinal type, float or string") + return for i in countup(1, sonsLen(n) - 1): var b = copyTree(n.sons[i]) addSon(a, b) @@ -620,12 +628,14 @@ proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int, checkMinSonsLen(b, 2, c.config) semCaseBranch(c, a, b, i, covered) of nkElse: - chckCovered = false checkSonsLen(b, 1, c.config) + if chckCovered and covered == toCover(c, a.sons[0].typ): + localError(c.config, b.info, "invalid else, all cases are already covered") + chckCovered = false else: illFormedAst(n, c.config) delSon(b, sonsLen(b) - 1) semRecordNodeAux(c, lastSon(n.sons[i]), check, pos, b, rectype, hasCaseFields = true) - if chckCovered and covered != lengthOrd(c.config, a.sons[0].typ): + if chckCovered and covered != toCover(c, a.sons[0].typ): localError(c.config, a.info, "not all cases are covered") addSon(father, a) -- cgit 1.4.1-2-gfad0 From abcf4d8b5dd4988c952a232d4a610d0491ff79ff Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Thu, 13 Dec 2018 12:45:47 +0530 Subject: Fix ftpclient.nim --- lib/deprecated/pure/ftpclient.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/deprecated/pure/ftpclient.nim b/lib/deprecated/pure/ftpclient.nim index d70f9556a..206c21f27 100644 --- a/lib/deprecated/pure/ftpclient.nim +++ b/lib/deprecated/pure/ftpclient.nim @@ -81,7 +81,6 @@ type oneSecond: BiggestInt # Bytes transferred in one second. lastProgressReport: float # Time toStore: string # Data left to upload (Only used with async) - else: nil FtpClientObj* = FtpBaseObj[Socket] FtpClient* = ref FtpClientObj -- cgit 1.4.1-2-gfad0 From fa4644006a618924985bc95bf899b60ea73d8c94 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Thu, 13 Dec 2018 14:12:22 +0530 Subject: Check for ordinal type --- compiler/semtypes.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index cb0402018..9961deee0 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -618,8 +618,8 @@ proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int, of tyFloat..tyFloat128, tyString, tyError: discard else: - localError(c.config, n.info, "selector must be of an ordinal type, float or string") - return + if not isOrdinalType(typ): + localError(c.config, n.info, "selector must be of an ordinal type, float or string") for i in countup(1, sonsLen(n) - 1): var b = copyTree(n.sons[i]) addSon(a, b) -- cgit 1.4.1-2-gfad0 From e6c510bbbd9fbd076a722c5b2b2626485a16b93e Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Thu, 13 Dec 2018 14:40:22 +0530 Subject: Check tyRange for exhaustiveness --- compiler/semtypes.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 9961deee0..d1e119de3 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -613,7 +613,7 @@ proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int, var chckCovered = false var typ = skipTypes(a.sons[0].typ, abstractVar-{tyTypeDesc}) case typ.kind - of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32, tyBool: + of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32, tyBool, tyRange: chckCovered = true of tyFloat..tyFloat128, tyString, tyError: discard -- cgit 1.4.1-2-gfad0 From 31cff752e0687b663d15ebfa71a57d5723c5b876 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Thu, 13 Dec 2018 11:28:50 +0530 Subject: Better error messages in enums which don't have ordinal types --- compiler/semtypes.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index f4ff97ba4..97ee062d1 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -91,7 +91,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType = strVal = v.sons[1] # second tuple part is the string value if skipTypes(strVal.typ, abstractInst).kind in {tyString, tyCString}: if not isOrdinalType(v.sons[0].typ): - localError(c.config, v.sons[0].info, errOrdinalTypeExpected) + localError(c.config, v.sons[0].info, errOrdinalTypeExpected & "; given: " & typeToString(v.sons[0].typ, preferDesc)) x = getOrdValue(v.sons[0]) # first tuple part is the ordinal else: localError(c.config, strVal.info, errStringLiteralExpected) @@ -102,7 +102,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType = x = counter else: if not isOrdinalType(v.typ): - localError(c.config, v.info, errOrdinalTypeExpected) + localError(c.config, v.info, errOrdinalTypeExpected & "; given: " & typeToString(v.typ, preferDesc)) x = getOrdValue(v) if i != 1: if x != counter: incl(result.flags, tfEnumHasHoles) -- cgit 1.4.1-2-gfad0 From a96928968e751be8066828b6c9b2ce8a993660c5 Mon Sep 17 00:00:00 2001 From: Ștefan Talpalaru Date: Thu, 13 Dec 2018 21:45:43 +0100 Subject: testament: individual tests with more than a subdir in their path e.g.: PATH="./bin:$PATH" ./koch tests r stdlib/nre/captures.nim --- testament/tester.nim | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/testament/tester.nim b/testament/tester.nim index 48e9f1913..a056f36d2 100644 --- a/testament/tester.nim +++ b/testament/tester.nim @@ -565,10 +565,12 @@ proc main() = p.next processCategory(r, cat, p.cmdLineRest.string, testsDir, runJoinableTests = false) of "r", "run": - let (dir, file) = splitPath(p.key.string) - let (_, subdir) = splitPath(dir) - var cat = Category(subdir) - processSingleTest(r, cat, p.cmdLineRest.string, file) + # at least one directory is required in the path, to use as a category name + let pathParts = split(p.key.string, {DirSep, AltSep}) + # "stdlib/nre/captures.nim" -> "stdlib" + "nre/captures.nim" + let cat = Category(pathParts[0]) + let subPath = joinPath(pathParts[1..^1]) + processSingleTest(r, cat, p.cmdLineRest.string, subPath) of "html": generateHtml(resultsFile, optFailing) else: -- cgit 1.4.1-2-gfad0 From ce9815bcf5a0ae5549be116604c2ad8ddacb17af Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 14 Dec 2018 08:57:55 +0100 Subject: os.nim: use the new pathnorm.normalizePath implementation --- changelog.md | 3 ++ lib/pure/os.nim | 49 ++++++++++++++++--------------- lib/pure/pathnorm.nim | 21 ++++++++----- tests/stdlib/tos.nim | 81 +++++++++++++++++---------------------------------- 4 files changed, 68 insertions(+), 86 deletions(-) diff --git a/changelog.md b/changelog.md index e48f72023..7fad8f822 100644 --- a/changelog.md +++ b/changelog.md @@ -111,6 +111,9 @@ proc enumToString*(enums: openArray[enum]): string = (default value: true) that can be set to `false` for better Posix interoperability. (Bug #9619.) +- `os.joinPath` and `os.normalizePath` handle edge cases like ``"a/b/../../.."`` + differently. + ### Language additions diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 6521d827c..1ad276b0a 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -984,36 +984,37 @@ proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [], noNimScr ## ## Warning: URL-encoded and Unicode attempts at directory traversal are not detected. ## Triple dot is not handled. - let isAbs = isAbsolute(path) - var stack: seq[string] = @[] - for p in split(path, {DirSep}): - case p - of "", ".": - continue - of "..": - if stack.len == 0: - if isAbs: - discard # collapse all double dots on absoluta paths - else: + path = pathnorm.normalizePath(path) + when false: + let isAbs = isAbsolute(path) + var stack: seq[string] = @[] + for p in split(path, {DirSep}): + case p + of "", ".": + continue + of "..": + if stack.len == 0: + if isAbs: + discard # collapse all double dots on absoluta paths + else: + stack.add(p) + elif stack[^1] == "..": stack.add(p) - elif stack[^1] == "..": - stack.add(p) + else: + discard stack.pop() else: - discard stack.pop() - else: - stack.add(p) + stack.add(p) - if isAbs: - path = DirSep & join(stack, $DirSep) - elif stack.len > 0: - path = join(stack, $DirSep) - else: - path = "." + if isAbs: + path = DirSep & join(stack, $DirSep) + elif stack.len > 0: + path = join(stack, $DirSep) + else: + path = "." proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [], noNimScript.} = ## Returns a normalized path for the current OS. See `<#normalizePath>`_ - result = path - normalizePath(result) + result = pathnorm.normalizePath(path) when defined(Windows) and not defined(nimscript): proc openHandle(path: string, followSymlink=true, writeAccess=false): Handle = diff --git a/lib/pure/pathnorm.nim b/lib/pure/pathnorm.nim index a33afefbd..4a7d74bf8 100644 --- a/lib/pure/pathnorm.nim +++ b/lib/pure/pathnorm.nim @@ -66,19 +66,26 @@ proc addNormalizePath*(x: string; result: var string; state: var int; dirSep = D if (state shr 1 == 0) and isSlash(x, b): result.add dirSep state = state or 1 - elif result.len > (state and 1) and isDotDot(x, b): - var d = result.len - # f/.. - while (d-1) > (state and 1) and result[d-1] notin {DirSep, AltSep}: - dec d - if d > 0: setLen(result, d-1) + elif isDotDot(x, b): + if (state shr 1) >= 1: + var d = result.len + # f/.. + while (d-1) > (state and 1) and result[d-1] notin {DirSep, AltSep}: + dec d + if d > 0: + setLen(result, d-1) + dec state, 2 + else: + if result.len > 0 and result[^1] notin {DirSep, AltSep}: + result.add dirSep + result.add substr(x, b[0], b[1]) elif isDot(x, b): discard "discard the dot" elif b[1] >= b[0]: if result.len > 0 and result[^1] notin {DirSep, AltSep}: result.add dirSep result.add substr(x, b[0], b[1]) - inc state, 2 + inc state, 2 proc normalizePath*(path: string; dirSep = DirSep): string = ## Example: diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim index 467f64fff..66ca3de33 100644 --- a/tests/stdlib/tos.nim +++ b/tests/stdlib/tos.nim @@ -190,61 +190,32 @@ block walkDirRec: removeDir("walkdir_test") block normalizedPath: - when defined(posix): - block relative: - doAssert normalizedPath(".") == "." - doAssert normalizedPath("..") == ".." - doAssert normalizedPath("../") == ".." - doAssert normalizedPath("../..") == "../.." - doAssert normalizedPath("../a/..") == ".." - doAssert normalizedPath("../a/../") == ".." - doAssert normalizedPath("./") == "." - - block absolute: - doAssert normalizedPath("/") == "/" - doAssert normalizedPath("/.") == "/" - doAssert normalizedPath("/..") == "/" - doAssert normalizedPath("/../") == "/" - doAssert normalizedPath("/../..") == "/" - doAssert normalizedPath("/../../") == "/" - doAssert normalizedPath("/../../../") == "/" - doAssert normalizedPath("/a/b/../../foo") == "/foo" - doAssert normalizedPath("/a/b/../../../foo") == "/foo" - doAssert normalizedPath("/./") == "/" - doAssert normalizedPath("//") == "/" - doAssert normalizedPath("///") == "/" - doAssert normalizedPath("/a//b") == "/a/b" - doAssert normalizedPath("/a///b") == "/a/b" - doAssert normalizedPath("/a/b/c/..") == "/a/b" - doAssert normalizedPath("/a/b/c/../") == "/a/b" - - else: - block relative: - doAssert normalizedPath(".") == "." - doAssert normalizedPath("..") == ".." - doAssert normalizedPath("..\\") == ".." - doAssert normalizedPath("..\\..") == "..\\.." - doAssert normalizedPath("..\\a\\..") == ".." - doAssert normalizedPath("..\\a\\..\\") == ".." - doAssert normalizedPath(".\\") == "." - - block absolute: - doAssert normalizedPath("\\") == "\\" - doAssert normalizedPath("\\.") == "\\" - doAssert normalizedPath("\\..") == "\\" - doAssert normalizedPath("\\..\\") == "\\" - doAssert normalizedPath("\\..\\..") == "\\" - doAssert normalizedPath("\\..\\..\\") == "\\" - doAssert normalizedPath("\\..\\..\\..\\") == "\\" - doAssert normalizedPath("\\a\\b\\..\\..\\foo") == "\\foo" - doAssert normalizedPath("\\a\\b\\..\\..\\..\\foo") == "\\foo" - doAssert normalizedPath("\\.\\") == "\\" - doAssert normalizedPath("\\\\") == "\\" - doAssert normalizedPath("\\\\\\") == "\\" - doAssert normalizedPath("\\a\\\\b") == "\\a\\b" - doAssert normalizedPath("\\a\\\\\\b") == "\\a\\b" - doAssert normalizedPath("\\a\\b\\c\\..") == "\\a\\b" - doAssert normalizedPath("\\a\\b\\c\\..\\") == "\\a\\b" + block relative: + doAssert normalizedPath(".") == "" + doAssert normalizedPath("..") == ".." + doAssert normalizedPath("../") == ".." + doAssert normalizedPath("../..") == unixToNativePath"../.." + doAssert normalizedPath("../a/..") == ".." + doAssert normalizedPath("../a/../") == ".." + doAssert normalizedPath("./") == "" + + block absolute: + doAssert normalizedPath("/") == unixToNativePath"/" + doAssert normalizedPath("/.") == unixToNativePath"/" + doAssert normalizedPath("/..") == unixToNativePath"/.." + doAssert normalizedPath("/../") == unixToNativePath"/.." + doAssert normalizedPath("/../..") == unixToNativePath"/../.." + doAssert normalizedPath("/../../") == unixToNativePath"/../.." + doAssert normalizedPath("/../../../") == unixToNativePath"/../../.." + doAssert normalizedPath("/a/b/../../foo") == unixToNativePath"/foo" + doAssert normalizedPath("/a/b/../../../foo") == unixToNativePath"/../foo" + doAssert normalizedPath("/./") == unixToNativePath"/" + doAssert normalizedPath("//") == unixToNativePath"/" + doAssert normalizedPath("///") == unixToNativePath"/" + doAssert normalizedPath("/a//b") == unixToNativePath"/a/b" + doAssert normalizedPath("/a///b") == unixToNativePath"/a/b" + doAssert normalizedPath("/a/b/c/..") == unixToNativePath"/a/b" + doAssert normalizedPath("/a/b/c/../") == unixToNativePath"/a/b" block isHidden: when defined(posix): -- cgit 1.4.1-2-gfad0 From 359a4b5fac53af35b499dfa3e484b131af6b0d19 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 14 Dec 2018 09:56:43 +0100 Subject: timers.nim: avoid parser warning --- lib/system/timers.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/system/timers.nim b/lib/system/timers.nim index dd8350e2d..4cd2fe840 100644 --- a/lib/system/timers.nim +++ b/lib/system/timers.nim @@ -49,7 +49,7 @@ elif defined(macosx): mach_timebase_info(timeBaseInfo) proc `-`(a, b: Ticks): Nanos = - result = (a.int64 - b.int64) * timeBaseInfo.numer div timeBaseInfo.denom + result = (a.int64 - b.int64) * timeBaseInfo.numer div timeBaseInfo.denom elif defined(posixRealtime): type -- cgit 1.4.1-2-gfad0 From f551b72fba30cb70bddfebad9d1398031b5ef620 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 14 Dec 2018 09:56:59 +0100 Subject: fixes #9965 --- compiler/cmdlinehelper.nim | 35 ++++++++++++---------- compiler/options.nim | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 15 deletions(-) diff --git a/compiler/cmdlinehelper.nim b/compiler/cmdlinehelper.nim index 9fbf4a0b0..7656a11f9 100644 --- a/compiler/cmdlinehelper.nim +++ b/compiler/cmdlinehelper.nim @@ -48,18 +48,26 @@ proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: Confi if self.suggestMode: conf.command = "nimsuggest" - # These defines/options should not be enabled while processing nimscript - # bug #4446, #9420, #8991, #9589, #9153 - undefSymbol(conf.symbols, "profiler") - undefSymbol(conf.symbols, "memProfiler") - undefSymbol(conf.symbols, "nodejs") - - # bug #9120 - conf.globalOptions.excl(optTaintMode) - - proc runNimScriptIfExists(path: AbsoluteFile)= - if fileExists(path): - runNimScript(cache, path, freshDefines = false, conf) + when false: + # These defines/options should not be enabled while processing nimscript + # bug #4446, #9420, #8991, #9589, #9153 + undefSymbol(conf.symbols, "profiler") + undefSymbol(conf.symbols, "memProfiler") + undefSymbol(conf.symbols, "nodejs") + + # bug #9120 + conf.globalOptions.excl(optTaintMode) + + template runNimScriptIfExists(path: AbsoluteFile) = + let p = path # eval once + if fileExists(p): + var tempConf = newConfigRef() + setDefaultLibpath(tempConf) + initDefines(tempConf.symbols) + tempConf.command = conf.command + tempConf.commandArgs = conf.commandArgs + runNimScript(cache, p, freshDefines = false, tempConf) + mergeConfigs(conf, tempConf) # Caution: make sure this stays in sync with `loadConfigs` if optSkipSystemConfigFile notin conf.globalOptions: @@ -88,9 +96,6 @@ proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: Confi # 'nimsuggest foo.nims' means to just auto-complete the NimScript file discard - # Reload configuration from .cfg file - loadConfigs(DefaultConfig, cache, conf) - # now process command line arguments again, because some options in the # command line can overwite the config file's settings extccomp.initVars(conf) diff --git a/compiler/options.nim b/compiler/options.nim index 80d665d62..52c7d88cd 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -253,6 +253,80 @@ template depConfigFields*(fn) {.dirty.} = fn(globalOptions) fn(selectedGC) +proc mergeConfigs*(dest, src: ConfigRef) = + template merge[T: enum](a, b: T) = + a = b + template merge[T](a, b: set[T]) = + a = a + b + template merge(a, b: int) = + inc a, b + template merge[T](a, b: seq[T]) = + for bb in b: a.add b + template merge(a, b: string) = + a = b + template merge[T: AbsoluteFile|AbsoluteDir](a, b: T) = + if a.isEmpty and not b.isEmpty: a = b + + template merge[T](a, b: HashSet[T]) = + for bb in b: a.incl b + template merge(a, b: StringTableRef) = + for k, v in b: a[k] = v + template merge[T: object](a, b: T) = + a = b + + template m(field) = + merge(dest.field, src.field) + + m target + m options + m globalOptions + m cmd + m selectedGC + m verbosity + m numberOfProcessors + m evalExpr + m symbolFiles + m cppDefines + m headerFile + m features + m arguments + m ideCmd + m cCompiler + m enableNotes + m disableNotes + m foreignPackageNotes + m notes + m errorCounter + m hintCounter + m warnCounter + m errorMax + m configVars + m symbols + m searchPaths + m lazyPaths + m outFile + m prefixDir + m libpath + m nimcacheDir + m dllOverrides + m moduleOverrides + m command + m commandArgs + m implicitImports + m implicitIncludes + m docSeeSrcUrl + m cIncludes + m cLibs + m cLinkedLibs + m externalToLink + m linkOptionsCmd + m compileOptionsCmd + m linkOptions + m compileOptions + m ccompilerpath + m toCompile + m cppCustomNamespace + const oldExperimentalFeatures* = {implicitDeref, dotOperators, callOperator, parallel} const -- cgit 1.4.1-2-gfad0 From 3ba8f158fb59372b492b353d60abeae343fdeef5 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 14 Dec 2018 11:41:19 +0100 Subject: added parseopt.remainingArgs; refs #9951 --- changelog.md | 1 + lib/pure/parseopt.nim | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/changelog.md b/changelog.md index 7fad8f822..f538744cf 100644 --- a/changelog.md +++ b/changelog.md @@ -91,6 +91,7 @@ proc enumToString*(enums: openArray[enum]): string = of two texts by line. - Added `os.relativePath`. +- Added `parseopt.remainingArgs`. ### Library changes diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim index fe3d3186f..8071fac93 100644 --- a/lib/pure/parseopt.nim +++ b/lib/pure/parseopt.nim @@ -234,6 +234,11 @@ when declared(os.paramCount): res.add quote(p.cmds[i]) result = res.TaintedString + proc remainingArgs*(p: OptParser): seq[TaintedString] {.rtl, extern: "npo$1".} = + ## retrieves the rest of the command line that has not been parsed yet. + result = @[] + for i in p.idx.. Date: Fri, 14 Dec 2018 05:44:11 -0500 Subject: Allow an escape hatch for platform specific flags/default override (#9968) * Allow an escape hatch for platform specific flags (of which there are many, for example MAP_POPULATE itself is a Linux-only thing, not other Unix). Continue with same defaults as before in this commit, but that really should be changed to *not* include MAP_POPULATE. While pre-faulting all the pages can be useful sometimes *if* you know you're going to access all the data, it is highly unlikely to be what users expect the default to be. For some things all that up front work is 1000s of times slower than being lazy/on-demand/only ever faulting parts of the file. Even the MAP_POPULATE fan who originally in 2014 committed to this file defaulted it to off (but turned it always-on as a "temporary" work around for some long since gone/mutated compiler issue). Anyway, at least something like this `mapFlags` gives users the ability to override the poor default choice or activate any other idiosyncratic platform-specific features. * Use simple, efficient default flags, but also accept whatever the open/mapMem caller specifies. Save flags in MemFile so they can be used in `resize`. This field should not need exporting like the others -- callers can always save whatever values they pass -- but we include a cautionary comment in case anyone ever asks for a `*` there, as well as for documentation. Also make documentation for ``mapFlags`` in open more likely to inspire care. --- lib/pure/memfiles.nim | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index 810223d72..d1a006eee 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -42,9 +42,10 @@ type wasOpened*: bool ## **Caution**: Windows specific public field. else: handle*: cint ## **Caution**: Posix specific public field. + flags: cint ## **Caution**: Platform specific private field. proc mapMem*(m: var MemFile, mode: FileMode = fmRead, - mappedSize = -1, offset = 0): pointer = + mappedSize = -1, offset = 0, mapFlags = cint(-1)): pointer = ## returns a pointer to a mapped portion of MemFile `m` ## ## ``mappedSize`` of ``-1`` maps to the whole file, and @@ -65,11 +66,17 @@ proc mapMem*(m: var MemFile, mode: FileMode = fmRead, raiseOSError(osLastError()) else: assert mappedSize > 0 + + m.flags = if mapFlags == cint(-1): MAP_SHARED else: mapFlags + #Ensure exactly one of MAP_PRIVATE cr MAP_SHARED is set + if int(m.flags and MAP_PRIVATE) == 0: + m.flags = m.flags or MAP_SHARED + result = mmap( nil, mappedSize, if readonly: PROT_READ else: PROT_READ or PROT_WRITE, - if readonly: (MAP_PRIVATE or MAP_POPULATE) else: (MAP_SHARED or MAP_POPULATE), + m.flags, m.handle, offset) if result == cast[pointer](MAP_FAILED): raiseOSError(osLastError()) @@ -90,7 +97,7 @@ proc unmapMem*(f: var MemFile, p: pointer, size: int) = proc open*(filename: string, mode: FileMode = fmRead, mappedSize = -1, offset = 0, newFileSize = -1, - allowRemap = false): MemFile = + allowRemap = false, mapFlags = cint(-1)): MemFile = ## opens a memory mapped file. If this fails, ``OSError`` is raised. ## ## ``newFileSize`` can only be set if the file does not exist and is opened @@ -105,6 +112,10 @@ proc open*(filename: string, mode: FileMode = fmRead, ## ``allowRemap`` only needs to be true if you want to call ``mapMem`` on ## the resulting MemFile; else file handles are not kept open. ## + ## ``mapFlags`` allows callers to override default choices for memory mapping + ## flags with a bitwise mask of a variety of likely platform-specific flags + ## which may be ignored or even cause `open` to fail if misspecified. + ## ## Example: ## ## .. code-block:: nim @@ -245,11 +256,16 @@ proc open*(filename: string, mode: FileMode = fmRead, else: fail(osLastError(), "error getting file size") + result.flags = if mapFlags == cint(-1): MAP_SHARED else: mapFlags + #Ensure exactly one of MAP_PRIVATE cr MAP_SHARED is set + if int(result.flags and MAP_PRIVATE) == 0: + result.flags = result.flags or MAP_SHARED + result.mem = mmap( nil, result.size, if readonly: PROT_READ else: PROT_READ or PROT_WRITE, - if readonly: (MAP_PRIVATE or MAP_POPULATE) else: (MAP_SHARED or MAP_POPULATE), + result.flags, result.handle, offset) @@ -305,7 +321,7 @@ when defined(posix) or defined(nimdoc): if munmap(f.mem, f.size) != 0: raiseOSError(osLastError()) let newAddr = mmap(nil, newFileSize, PROT_READ or PROT_WRITE, - MAP_SHARED or MAP_POPULATE, f.handle, 0) + f.flags, f.handle, 0) if newAddr == cast[pointer](MAP_FAILED): raiseOSError(osLastError()) f.mem = newAddr -- cgit 1.4.1-2-gfad0 From 32c6b35d97332e884648929672b0f163ee6febff Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 14 Dec 2018 12:00:49 +0100 Subject: fixes yet another NimScript regression --- compiler/cmdlinehelper.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/cmdlinehelper.nim b/compiler/cmdlinehelper.nim index 7656a11f9..13090ce9c 100644 --- a/compiler/cmdlinehelper.nim +++ b/compiler/cmdlinehelper.nim @@ -66,6 +66,8 @@ proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: Confi initDefines(tempConf.symbols) tempConf.command = conf.command tempConf.commandArgs = conf.commandArgs + tempConf.searchPaths = conf.searchPaths + tempConf.lazyPaths = conf.lazyPaths runNimScript(cache, p, freshDefines = false, tempConf) mergeConfigs(conf, tempConf) -- cgit 1.4.1-2-gfad0 From 675b6f07737a0f23b3b052552eeec525136c6960 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Fri, 14 Dec 2018 17:13:41 +0100 Subject: fixes #9978 --- compiler/passes.nim | 2 +- tests/modules/timport_in_config.nim | 8 ++++++++ tests/modules/timport_in_config.nim.cfg | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 tests/modules/timport_in_config.nim create mode 100644 tests/modules/timport_in_config.nim.cfg diff --git a/compiler/passes.nim b/compiler/passes.nim index 9ccd2240a..6863576cd 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -155,7 +155,7 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool { while true: openParsers(p, fileIdx, s, graph.cache, graph.config) - if sfSystemModule notin module.flags: + if graph.config.mainPackageId == module.owner.id: # XXX what about caching? no processing then? what if I change the # modules to include between compilation runs? we'd need to track that # in ROD files. I think we should enable this feature only diff --git a/tests/modules/timport_in_config.nim b/tests/modules/timport_in_config.nim new file mode 100644 index 000000000..1ea02407d --- /dev/null +++ b/tests/modules/timport_in_config.nim @@ -0,0 +1,8 @@ +discard """ +output: '''hallo''' +joinable: false +""" + +# bug #9978 + +echo "hi".replace("i", "allo") diff --git a/tests/modules/timport_in_config.nim.cfg b/tests/modules/timport_in_config.nim.cfg new file mode 100644 index 000000000..50b6679f8 --- /dev/null +++ b/tests/modules/timport_in_config.nim.cfg @@ -0,0 +1 @@ +--import: "strutils" -- cgit 1.4.1-2-gfad0 From 5ba22c9315a46a281baa2baf5a3ab4abdfcc4a33 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Fri, 14 Dec 2018 17:14:55 +0100 Subject: document the change for fix #9978 --- changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.md b/changelog.md index f538744cf..353d07205 100644 --- a/changelog.md +++ b/changelog.md @@ -66,6 +66,9 @@ proc enumToString*(enums: openArray[enum]): string = ``` - ``discard x`` is now illegal when `x` is a function symbol. +- Implicit imports via ``--import: module`` in a config file are now restricted + to the main package. + ### Library additions -- cgit 1.4.1-2-gfad0 From f76bd06643d0b8ee4de18808eb73d78e9aa91270 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Fri, 14 Dec 2018 20:03:12 +0100 Subject: fixes #9982 --- compiler/options.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/options.nim b/compiler/options.nim index 52c7d88cd..7e853d646 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -282,7 +282,7 @@ proc mergeConfigs*(dest, src: ConfigRef) = m globalOptions m cmd m selectedGC - m verbosity + dest.verbosity = src.verbosity m numberOfProcessors m evalExpr m symbolFiles -- cgit 1.4.1-2-gfad0 From 446f911a173a595648fa444c83d931193198eeb6 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Sat, 15 Dec 2018 09:09:46 +0100 Subject: better error message for 'proc' that is not intended to be used as a typeclass --- compiler/semstmts.nim | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 5be57f614..2d98d84e1 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1090,9 +1090,13 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) = proc checkForMetaFields(c: PContext; n: PNode) = - template checkMeta(t) = + proc checkMeta(c: PContext; n: PNode; t: PType) = if t != nil and t.isMetaType and tfGenericTypeParam notin t.flags: - localError(c.config, n.info, errTIsNotAConcreteType % t.typeToString) + if t.kind == tyBuiltInTypeClass and t.len == 1 and t.sons[0].kind == tyProc: + localError(c.config, n.info, ("'$1' is not a concrete type; " & + "for a callback without parameters use 'proc()'") % t.typeToString) + else: + localError(c.config, n.info, errTIsNotAConcreteType % t.typeToString) if n.isNil: return case n.kind @@ -1107,9 +1111,9 @@ proc checkForMetaFields(c: PContext; n: PNode) = tyProc, tyGenericInvocation, tyGenericInst, tyAlias, tySink: let start = ord(t.kind in {tyGenericInvocation, tyGenericInst}) for i in start ..< t.len: - checkMeta(t.sons[i]) + checkMeta(c, n, t.sons[i]) else: - checkMeta(t) + checkMeta(c, n, t) else: internalAssert c.config, false -- cgit 1.4.1-2-gfad0 From e3a668a33baaf9d89b287827eaab3fa1cdfec877 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Sat, 15 Dec 2018 13:54:41 +0100 Subject: --gc:destructors: baby steps --- compiler/destroyer.nim | 23 ++++++++++++----------- compiler/semstmts.nim | 9 +++++++++ compiler/semtypes.nim | 5 +---- lib/core/seqs.nim | 6 ------ lib/system.nim | 9 +++++---- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/compiler/destroyer.nim b/compiler/destroyer.nim index 40af11e70..03ce9a5cf 100644 --- a/compiler/destroyer.nim +++ b/compiler/destroyer.nim @@ -296,7 +296,8 @@ proc makePtrType(c: Con, baseType: PType): PType = template genOp(opr, opname, ri) = let op = opr if op == nil: - globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator not found for type " & typeToString(t)) + globalError(c.graph.config, dest.info, "internal error: '" & opname & + "' operator not found for type " & typeToString(t)) elif op.ast[genericParamsPos].kind != nkEmpty: globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator is generic") patchHead op @@ -365,7 +366,7 @@ proc destructiveMoveVar(n: PNode; c: var Con): PNode = result.add genWasMoved(n, c) result.add tempAsNode -proc sinkParamIsLastReadCheck(c: var Con, s: PNode) = +proc sinkParamIsLastReadCheck(c: var Con, s: PNode) = assert s.kind == nkSym and s.sym.kind == skParam if not isLastRead(s, c): localError(c.graph.config, c.otherRead.info, "sink parameter `" & $s.sym.name.s & @@ -427,7 +428,7 @@ proc pArg(arg: PNode; c: var Con; isSink: bool): PNode = result = copyNode(arg) for i in 0.. Date: Sat, 15 Dec 2018 14:01:18 +0100 Subject: fixes #9995 --- compiler/cmdlinehelper.nim | 7 ++----- compiler/options.nim | 5 +++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/cmdlinehelper.nim b/compiler/cmdlinehelper.nim index 13090ce9c..fa1eb3816 100644 --- a/compiler/cmdlinehelper.nim +++ b/compiler/cmdlinehelper.nim @@ -64,12 +64,9 @@ proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: Confi var tempConf = newConfigRef() setDefaultLibpath(tempConf) initDefines(tempConf.symbols) - tempConf.command = conf.command - tempConf.commandArgs = conf.commandArgs - tempConf.searchPaths = conf.searchPaths - tempConf.lazyPaths = conf.lazyPaths + mergeConfigs(tempConf, conf, mergeSymbols = false) runNimScript(cache, p, freshDefines = false, tempConf) - mergeConfigs(conf, tempConf) + mergeConfigs(conf, tempConf, mergeSymbols = true) # Caution: make sure this stays in sync with `loadConfigs` if optSkipSystemConfigFile notin conf.globalOptions: diff --git a/compiler/options.nim b/compiler/options.nim index 7e853d646..65f2b907a 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -253,7 +253,7 @@ template depConfigFields*(fn) {.dirty.} = fn(globalOptions) fn(selectedGC) -proc mergeConfigs*(dest, src: ConfigRef) = +proc mergeConfigs*(dest, src: ConfigRef; mergeSymbols: bool) = template merge[T: enum](a, b: T) = a = b template merge[T](a, b: set[T]) = @@ -301,7 +301,8 @@ proc mergeConfigs*(dest, src: ConfigRef) = m warnCounter m errorMax m configVars - m symbols + if mergeSymbols: + m symbols m searchPaths m lazyPaths m outFile -- cgit 1.4.1-2-gfad0 From a87be403397e9be86395c76fdcdf49269898fdb4 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Sat, 15 Dec 2018 14:09:06 +0100 Subject: fixes #9985 --- compiler/options.nim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/options.nim b/compiler/options.nim index 65f2b907a..7569598d0 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -303,6 +303,9 @@ proc mergeConfigs*(dest, src: ConfigRef; mergeSymbols: bool) = m configVars if mergeSymbols: m symbols + m projectName + m projectPath + m projectFull m searchPaths m lazyPaths m outFile -- cgit 1.4.1-2-gfad0 From 50193b95eaa48e87b7f20f6d2803699cb73821d6 Mon Sep 17 00:00:00 2001 From: alaviss Date: Sat, 15 Dec 2018 20:10:44 +0700 Subject: fix experimental/diff documentation rendering (#9983) also remove references to non-existant parameters --- lib/experimental/diff.nim | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/experimental/diff.nim b/lib/experimental/diff.nim index bffce2803..253355707 100644 --- a/lib/experimental/diff.nim +++ b/lib/experimental/diff.nim @@ -252,8 +252,11 @@ proc createDiffs(dataA, dataB: DiffData): seq[Item] = proc diffInt*(arrayA, arrayB: openArray[int]): seq[Item] = ## Find the difference in 2 arrays of integers. + ## ## ``arrayA`` A-version of the numbers (usualy the old one) + ## ## ``arrayB`` B-version of the numbers (usualy the new one) + ## ## Returns a array of Items that describe the differences. # The A-Version of the data (original data) to be compared. @@ -273,15 +276,16 @@ proc diffInt*(arrayA, arrayB: openArray[int]): seq[Item] = proc diffText*(textA, textB: string): seq[Item] = ## Find the difference in 2 text documents, comparing by textlines. + ## ## The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents ## each line is converted into a (hash) number. This hash-value is computed by storing all ## textlines into a common hashtable so i can find dublicates in there, and generating a ## new number each time a new textline is inserted. - ## ``TextA`` A-version of the text (usualy the old one) - ## ``TextB`` B-version of the text (usualy the new one) - ## ``trimSpace`` When set to true, all leading and trailing whitespace characters are stripped out before the comparation is done. - ## ``ignoreSpace`` When set to true, all whitespace characters are converted to a single space character before the comparation is done. - ## ``ignoreCase`` When set to true, all characters are converted to their lowercase equivivalence before the comparation is done. + ## + ## ``textA`` A-version of the text (usually the old one) + ## + ## ``textB`` B-version of the text (usually the new one) + ## ## Returns a seq of Items that describe the differences. # prepare the input-text and convert to comparable numbers. -- cgit 1.4.1-2-gfad0 From b53327c92a1c685ab71917cabedbf441f7f5fd2d Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Sat, 15 Dec 2018 14:30:07 +0100 Subject: fixes #9994 --- compiler/passes.nim | 4 ++-- tests/modules/mimport_in_config.nim | 2 ++ tests/modules/timport_in_config.nim | 3 ++- tests/modules/timport_in_config.nim.cfg | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 tests/modules/mimport_in_config.nim diff --git a/compiler/passes.nim b/compiler/passes.nim index 6863576cd..d9a8fb1a7 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -102,9 +102,9 @@ proc processImplicits(graph: ModuleGraph; implicits: seq[string], nodeKind: TNod for module in items(implicits): # implicit imports should not lead to a module importing itself if m.position != resolveMod(graph.config, module, relativeTo).int32: - var importStmt = newNodeI(nodeKind, gCmdLineInfo) + var importStmt = newNodeI(nodeKind, m.info) var str = newStrNode(nkStrLit, module) - str.info = gCmdLineInfo + str.info = m.info importStmt.addSon str if not processTopLevelStmt(graph, importStmt, a): break diff --git a/tests/modules/mimport_in_config.nim b/tests/modules/mimport_in_config.nim new file mode 100644 index 000000000..555b6074d --- /dev/null +++ b/tests/modules/mimport_in_config.nim @@ -0,0 +1,2 @@ +type + DefinedInB* = int diff --git a/tests/modules/timport_in_config.nim b/tests/modules/timport_in_config.nim index 1ea02407d..847b063bd 100644 --- a/tests/modules/timport_in_config.nim +++ b/tests/modules/timport_in_config.nim @@ -3,6 +3,7 @@ output: '''hallo''' joinable: false """ -# bug #9978 +# bug #9978, #9994 +var x: DefinedInB echo "hi".replace("i", "allo") diff --git a/tests/modules/timport_in_config.nim.cfg b/tests/modules/timport_in_config.nim.cfg index 50b6679f8..2633e1012 100644 --- a/tests/modules/timport_in_config.nim.cfg +++ b/tests/modules/timport_in_config.nim.cfg @@ -1 +1,2 @@ --import: "strutils" +--import: "mimport_in_config" -- cgit 1.4.1-2-gfad0 From c673607d36d618649524bbf96008f487213ef999 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Sat, 15 Dec 2018 22:19:39 +0530 Subject: Fix internal error when casting to invalid enum value --- compiler/semfold.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 9e7ed5cee..0bdd0b64c 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -132,7 +132,9 @@ proc ordinalValToString*(a: PNode; g: ModuleGraph): string = return field.name.s else: return field.ast.strVal - internalError(g.config, a.info, "no symbol for ordinal value: " & $x) + localError(g.config, a.info, + "Cannot convert int literal to $1. The value is invalid." % + [typeToString(t)]) else: result = $x -- cgit 1.4.1-2-gfad0 From 19f5835d5c12b0ba77f58959b642c85cfdbba995 Mon Sep 17 00:00:00 2001 From: recloser <44084068+recloser@users.noreply.github.com> Date: Sat, 15 Dec 2018 22:57:18 +0100 Subject: Fixes #10005 --- compiler/jsgen.nim | 49 ++++++++++++++++--------------------------------- tests/js/testobjs.nim | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 4d22c4224..cd51aaddd 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -918,35 +918,15 @@ proc countJsParams(typ: PType): int = const nodeKindsNeedNoCopy = {nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit, nkFloatLit..nkFloat64Lit, nkCurly, nkPar, nkStringToCString, + nkObjConstr, nkTupleConstr, nkBracket, nkCStringToString, nkCall, nkPrefix, nkPostfix, nkInfix, nkCommand, nkHiddenCallConv, nkCallStrLit} proc needsNoCopy(p: PProc; y: PNode): bool = - # if the node is a literal object constructor we have to recursively - # check the expressions passed into it - case y.kind - of nkObjConstr: - for arg in y.sons[1..^1]: - if not needsNoCopy(p, arg[1]): - return false - of nkTupleConstr: - for arg in y.sons: - var arg = arg - if arg.kind == nkExprColonExpr: - arg = arg[1] - if not needsNoCopy(p, arg): - return false - of nkBracket: - for arg in y.sons: - if not needsNoCopy(p, arg): - return false - of nodeKindsNeedNoCopy: - return true - else: - return (mapType(y.typ) != etyBaseIndex and - (skipTypes(y.typ, abstractInst).kind in - {tyRef, tyPtr, tyLent, tyVar, tyCString, tyProc} + IntegralTypes)) - return true + return y.kind in nodeKindsNeedNoCopy or + (mapType(y.typ) != etyBaseIndex and + (skipTypes(y.typ, abstractInst).kind in + {tyRef, tyPtr, tyLent, tyVar, tyCString, tyProc} + IntegralTypes)) proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = var a, b: TCompRes @@ -2005,6 +1985,10 @@ proc genArrayConstr(p: PProc, n: PNode, r: var TCompRes) = if a.typ == etyBaseIndex: addf(r.res, "[$1, $2]", [a.address, a.res]) else: + if not needsNoCopy(p, n[i]): + let typ = n[i].typ.skipTypes(abstractInst) + useMagic(p, "nimCopy") + a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)] add(r.res, a.res) add(r.res, "]") @@ -2017,9 +2001,13 @@ proc genTupleConstr(p: PProc, n: PNode, r: var TCompRes) = var it = n.sons[i] if it.kind == nkExprColonExpr: it = it.sons[1] gen(p, it, a) + let typ = it.typ.skipTypes(abstractInst) if a.typ == etyBaseIndex: addf(r.res, "Field$#: [$#, $#]", [i.rope, a.address, a.res]) else: + if not needsNoCopy(p, it): + useMagic(p, "nimCopy") + a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)] addf(r.res, "Field$#: $#", [i.rope, a.res]) r.res.add("}") @@ -2039,17 +2027,12 @@ proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) = fieldIDs.incl(f.id) let typ = val.typ.skipTypes(abstractInst) - if (typ.kind in IntegralTypes+{tyCstring, tyRef, tyPtr} and - mapType(p, typ) != etyBaseIndex) or - a.typ == etyBaseIndex or - needsNoCopy(p, it.sons[1]): - discard - else: - useMagic(p, "nimCopy") - a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)] if a.typ == etyBaseIndex: addf(initList, "$#: [$#, $#]", [f.loc.r, a.address, a.res]) else: + if not needsNoCopy(p, val): + useMagic(p, "nimCopy") + a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)] addf(initList, "$#: $#", [f.loc.r, a.res]) let t = skipTypes(n.typ, abstractInst + skipPtrs) createObjInitList(p, t, fieldIDs, initList) diff --git a/tests/js/testobjs.nim b/tests/js/testobjs.nim index 78f0b4766..b61d06471 100644 --- a/tests/js/testobjs.nim +++ b/tests/js/testobjs.nim @@ -54,3 +54,20 @@ let test2 = test1 echo toJSON(test1) echo toJSON(test2) + +block issue10005: + type + Player = ref object of RootObj + id*: string + nickname*: string + color*: string + + proc newPlayer(nickname: string, color: string): Player = + let pl = Player(color: "#123", nickname: nickname) + return Player( + id: "foo", + nickname: nickname, + color: color, + ) + + doAssert newPlayer("foo", "#1232").nickname == "foo" -- cgit 1.4.1-2-gfad0 From c99407b07a1ed5a09a518dca0690732287b6605f Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Sun, 16 Dec 2018 11:39:29 +0530 Subject: Add project directory to include path --- compiler/extccomp.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index 23f723e29..99e2534ec 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -567,6 +567,8 @@ proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile): string = includeCmd = "" compilePattern = getCompilerExe(conf, c, cfile.cname) + includeCmd.add(join([CC[c].includeCmd, conf.projectPath.string])) + var cf = if noAbsolutePaths(conf): AbsoluteFile extractFilename(cfile.cname.string) else: cfile.cname -- cgit 1.4.1-2-gfad0 From 303ef57b1592eaf2131e15143e79187c01096dca Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Sun, 16 Dec 2018 13:38:31 +0530 Subject: Fix clang not producing debug info in cpp mode (#9976) * Fix clang on windows to produce debug info * Fix clang not producing debug info in cpp mode --- config/nim.cfg | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config/nim.cfg b/config/nim.cfg index 38683b304..e6175784e 100644 --- a/config/nim.cfg +++ b/config/nim.cfg @@ -220,10 +220,20 @@ llvm_gcc.options.size = "-Os" # Configuration for the LLVM CLang compiler: clang.options.debug = "-g" +clang.cpp.options.debug = "-g" clang.options.always = "-w" clang.options.speed = "-O3" clang.options.size = "-Os" +@if windows: + clang.options.debug = "-g -gcodeview" + clang.cpp.options.debug = "-g -gcodeview" + @if not release: + clang.options.linker = "-g" + clang.cpp.options.linker = "-g" + @end +@end + # Configuration for the Visual C/C++ compiler: # VCCEXE is a tool that invokes the Visual Studio Developer Command Prompt # before calling the compiler. -- cgit 1.4.1-2-gfad0 From e70c6af1882ad1a13567993a7a06b131fd6c9025 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Sun, 16 Dec 2018 14:42:57 +0530 Subject: escape the include path --- compiler/extccomp.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index 99e2534ec..69705db43 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -567,7 +567,7 @@ proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile): string = includeCmd = "" compilePattern = getCompilerExe(conf, c, cfile.cname) - includeCmd.add(join([CC[c].includeCmd, conf.projectPath.string])) + includeCmd.add(join([CC[c].includeCmd, quoteShell(conf.projectPath.string)])) var cf = if noAbsolutePaths(conf): AbsoluteFile extractFilename(cfile.cname.string) else: cfile.cname -- cgit 1.4.1-2-gfad0 From 6e5c2af203895da094171f3b7b81fbeaece1654a Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 16 Dec 2018 18:01:59 +0100 Subject: fixes #9991 --- compiler/passes.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/passes.nim b/compiler/passes.nim index d9a8fb1a7..9302bb2e4 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -155,7 +155,7 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool { while true: openParsers(p, fileIdx, s, graph.cache, graph.config) - if graph.config.mainPackageId == module.owner.id: + if module.owner == nil or module.owner.name.s != "stdlib": # graph.config.mainPackageId == module.owner.id: # XXX what about caching? no processing then? what if I change the # modules to include between compilation runs? we'd need to track that # in ROD files. I think we should enable this feature only -- cgit 1.4.1-2-gfad0 From bae662fa8a5ab005a13ff7f5a0d08f22b14cb6ec Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 16 Dec 2018 18:44:29 +0100 Subject: restore the old .nims processing behaviour; fixes #9989; fixes #9995 --- compiler/cmdlinehelper.nim | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/compiler/cmdlinehelper.nim b/compiler/cmdlinehelper.nim index fa1eb3816..5e7afa0ba 100644 --- a/compiler/cmdlinehelper.nim +++ b/compiler/cmdlinehelper.nim @@ -61,12 +61,7 @@ proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: Confi template runNimScriptIfExists(path: AbsoluteFile) = let p = path # eval once if fileExists(p): - var tempConf = newConfigRef() - setDefaultLibpath(tempConf) - initDefines(tempConf.symbols) - mergeConfigs(tempConf, conf, mergeSymbols = false) - runNimScript(cache, p, freshDefines = false, tempConf) - mergeConfigs(conf, tempConf, mergeSymbols = true) + runNimScript(cache, p, freshDefines = false, conf) # Caution: make sure this stays in sync with `loadConfigs` if optSkipSystemConfigFile notin conf.globalOptions: -- cgit 1.4.1-2-gfad0 From 7ec77684e9d4c429efae64e9141bf549faef3865 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 16 Dec 2018 19:17:09 +0100 Subject: fixes #9153 --- lib/system.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/system.nim b/lib/system.nim index 47efda3fc..d51f9e899 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1555,7 +1555,7 @@ const ## is the value that should be passed to `quit <#quit>`_ to indicate ## failure. -when defined(nodejs): +when defined(nodejs) and not defined(nimscript): var programResult* {.importc: "process.exitCode".}: int programResult = 0 else: @@ -1601,7 +1601,7 @@ elif defined(genode): -elif defined(nodejs): +elif defined(nodejs) and not defined(nimscript): proc quit*(errorcode: int = QuitSuccess) {.magic: "Exit", importc: "process.exit", noreturn.} @@ -3610,7 +3610,7 @@ elif defined(JS): proc deallocShared(p: pointer) = discard proc reallocShared(p: pointer, newsize: Natural): pointer = discard - when defined(JS): + when defined(JS) and not defined(nimscript): include "system/jssys" include "system/reprjs" elif defined(nimscript): -- cgit 1.4.1-2-gfad0 From d66eb04ce9f5da6149744a07ca3b9adbfe9705a6 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 16 Dec 2018 19:22:49 +0100 Subject: fixes #8991, fixes #4446 --- compiler/cmdlinehelper.nim | 2 +- lib/system.nim | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/cmdlinehelper.nim b/compiler/cmdlinehelper.nim index 5e7afa0ba..bf7100e34 100644 --- a/compiler/cmdlinehelper.nim +++ b/compiler/cmdlinehelper.nim @@ -50,7 +50,7 @@ proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: Confi when false: # These defines/options should not be enabled while processing nimscript - # bug #4446, #9420, #8991, #9589, #9153 + # bug #4446, #9420, #8991 undefSymbol(conf.symbols, "profiler") undefSymbol(conf.symbols, "memProfiler") undefSymbol(conf.symbols, "nodejs") diff --git a/lib/system.nim b/lib/system.nim index d51f9e899..2497d39a6 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1539,7 +1539,7 @@ else: ## ``string`` if the taint mode is not ## turned on. -when defined(profiler): +when defined(profiler) and not defined(nimscript): proc nimProfile() {.compilerProc, noinline.} when hasThreadSupport: {.pragma: rtlThreadVar, threadvar.} @@ -3569,7 +3569,7 @@ when not defined(JS): #and not defined(nimscript): when defined(endb) and not defined(nimscript): include "system/debugger" - when defined(profiler) or defined(memProfiler): + when (defined(profiler) or defined(memProfiler)) and not defined(nimscript): include "system/profiler" {.pop.} # stacktrace -- cgit 1.4.1-2-gfad0 From d91d1865b88409ba7fc307505eff69c156d74d52 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 16 Dec 2018 20:11:04 +0100 Subject: fixes #9420 --- compiler/cmdlinehelper.nim | 2 +- lib/system/profiler.nim | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/cmdlinehelper.nim b/compiler/cmdlinehelper.nim index bf7100e34..a15d622a0 100644 --- a/compiler/cmdlinehelper.nim +++ b/compiler/cmdlinehelper.nim @@ -50,7 +50,7 @@ proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: Confi when false: # These defines/options should not be enabled while processing nimscript - # bug #4446, #9420, #8991 + # bug #9420 undefSymbol(conf.symbols, "profiler") undefSymbol(conf.symbols, "memProfiler") undefSymbol(conf.symbols, "nodejs") diff --git a/lib/system/profiler.nim b/lib/system/profiler.nim index ffd6fd0c5..57b8af42d 100644 --- a/lib/system/profiler.nim +++ b/lib/system/profiler.nim @@ -57,13 +57,13 @@ proc captureStackTrace(f: PFrame, st: var StackTrace) = b = b.prev var - profilingRequestedHook*: proc (): bool {.nimcall, benign.} + profilingRequestedHook*: proc (): bool {.nimcall, locks: 0, gcsafe.} ## set this variable to provide a procedure that implements a profiler in ## user space. See the `nimprof` module for a reference implementation. when defined(memProfiler): type - MemProfilerHook* = proc (st: StackTrace, requestedSize: int) {.nimcall, benign.} + MemProfilerHook* = proc (st: StackTrace, requestedSize: int) {.nimcall, locks: 0, gcsafe.} var profilerHook*: MemProfilerHook @@ -87,9 +87,10 @@ else: proc callProfilerHook(hook: ProfilerHook) {.noinline.} = # 'noinline' so that 'nimProfile' does not perform the stack allocation # in the common case. - var st: StackTrace - captureStackTrace(framePtr, st) - hook(st) + when not defined(nimdoc): + var st: StackTrace + captureStackTrace(framePtr, st) + hook(st) proc nimProfile() = ## This is invoked by the compiler in every loop and on every proc entry! -- cgit 1.4.1-2-gfad0 From 9526009e0eb89b3ce2b881cb35384f9b3ef1e02c Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 16 Dec 2018 20:34:07 +0100 Subject: fixes #9120 --- compiler/cmdlinehelper.nim | 10 ---------- lib/pure/parseopt.nim | 2 +- lib/system.nim | 2 +- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/compiler/cmdlinehelper.nim b/compiler/cmdlinehelper.nim index a15d622a0..e1824316a 100644 --- a/compiler/cmdlinehelper.nim +++ b/compiler/cmdlinehelper.nim @@ -48,16 +48,6 @@ proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: Confi if self.suggestMode: conf.command = "nimsuggest" - when false: - # These defines/options should not be enabled while processing nimscript - # bug #9420 - undefSymbol(conf.symbols, "profiler") - undefSymbol(conf.symbols, "memProfiler") - undefSymbol(conf.symbols, "nodejs") - - # bug #9120 - conf.globalOptions.excl(optTaintMode) - template runNimScriptIfExists(path: AbsoluteFile) = let p = path # eval once if fileExists(p): diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim index 8071fac93..eba915604 100644 --- a/lib/pure/parseopt.nim +++ b/lib/pure/parseopt.nim @@ -237,7 +237,7 @@ when declared(os.paramCount): proc remainingArgs*(p: OptParser): seq[TaintedString] {.rtl, extern: "npo$1".} = ## retrieves the rest of the command line that has not been parsed yet. result = @[] - for i in p.idx..`_ and closes the file afterwards. Returns the string. ## Raises an IO exception in case of an error. If # you need to call -- cgit 1.4.1-2-gfad0 From 86c3ac6a611d7e4eaa9eb017036e8ef3b20b0cd9 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 16 Dec 2018 20:53:50 +0100 Subject: see what breaks with a global config.nims --- config/config.nims | 1 + 1 file changed, 1 insertion(+) create mode 100644 config/config.nims diff --git a/config/config.nims b/config/config.nims new file mode 100644 index 000000000..3f0d9e1b8 --- /dev/null +++ b/config/config.nims @@ -0,0 +1 @@ +# empty config.nims to prevent future regressions, see #9990 -- cgit 1.4.1-2-gfad0 From 915a9ec08259c2cceccd4f46c8ed51742316390d Mon Sep 17 00:00:00 2001 From: Istvan Date: Mon, 17 Dec 2018 19:29:08 +0100 Subject: Adding more documentation to logging.nim Just minor addition to the documentation with the use of operators --- lib/pure/logging.nim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim index cd13deec3..febd0b602 100644 --- a/lib/pure/logging.nim +++ b/lib/pure/logging.nim @@ -12,7 +12,7 @@ ## write your own. ## ## Format strings support the following variables which must be prefixed with -## the dollar operator (``$``): +## the dollar operator (``$``, see example below): ## ## ============ ======================= ## Operator Output @@ -43,6 +43,11 @@ ## warn("4 8 15 16 23 4-- Error") ## error("922044:16 SYSTEM FAILURE") ## fatal("SYSTEM FAILURE SYSTEM FAILURE") +## # Using the aformetioned operator +## var opL = newConsoleLogger(fmtStr = "$datetime :: ") +## addHandler(opL) +## info("Starting web server...") +## # Will print something like 2018-12-17T19:28:05 :: Starting web server... ## ## **Warning:** The global list of handlers is a thread var, this means that ## the handlers must be re-added in each thread. -- cgit 1.4.1-2-gfad0 From 09b9a544187f9ab72da675fc80a5c815c0a6535c Mon Sep 17 00:00:00 2001 From: Araq Date: Mon, 17 Dec 2018 23:03:54 +0100 Subject: make tests green with a global config.nims file --- compiler/commands.nim | 6 ++++-- lib/system/ansi_c.nim | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/commands.nim b/compiler/commands.nim index b090a09a5..5893791cc 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -590,10 +590,12 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; processOnOffSwitchG(conf, {optGenIndex}, arg, pass, info) of "import": expectArg(conf, switch, arg, pass, info) - if pass in {passCmd2, passPP}: conf.implicitImports.add arg + if pass in {passCmd2, passPP}: + conf.implicitImports.add findModule(conf, arg, toFullPath(conf, info)).string of "include": expectArg(conf, switch, arg, pass, info) - if pass in {passCmd2, passPP}: conf.implicitIncludes.add arg + if pass in {passCmd2, passPP}: + conf.implicitIncludes.add findModule(conf, arg, toFullPath(conf, info)).string of "listcmd": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optListCmd) diff --git a/lib/system/ansi_c.nim b/lib/system/ansi_c.nim index 38910dbd6..af34060d8 100644 --- a/lib/system/ansi_c.nim +++ b/lib/system/ansi_c.nim @@ -51,7 +51,7 @@ when defined(windows): elif defined(macosx) or defined(linux) or defined(freebsd) or defined(openbsd) or defined(netbsd) or defined(solaris) or defined(dragonfly) or defined(nintendoswitch) or defined(genode) or - defined(aix): + defined(aix) or hostOS == "standalone": const SIGABRT = cint(6) SIGFPE = cint(8) -- cgit 1.4.1-2-gfad0 From c4e3c4ca2d0a1f44ed1e3dd9db564b66031f0843 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 18 Dec 2018 00:07:12 -0800 Subject: add `getCurrentCompilerExe` to vmops (eg allows to get nim compiler at CT); add tests for vmops (#9925) --- changelog.md | 3 +++ compiler/vmops.nim | 5 ++++- lib/pure/os.nim | 9 ++++++++- lib/system/nimscript.nim | 1 + tests/vm/tgorge.nim | 2 ++ tests/vm/tvmops.nim | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 tests/vm/tvmops.nim diff --git a/changelog.md b/changelog.md index 353d07205..ecdd26086 100644 --- a/changelog.md +++ b/changelog.md @@ -95,6 +95,9 @@ proc enumToString*(enums: openArray[enum]): string = - Added `os.relativePath`. - Added `parseopt.remainingArgs`. +- Added `os.getCurrentCompilerExe` (implmented as `getAppFilename` at CT), + can be used to retrive the currently executing compiler. + ### Library changes diff --git a/compiler/vmops.nim b/compiler/vmops.nim index 75873bfe8..56c97dec6 100644 --- a/compiler/vmops.nim +++ b/compiler/vmops.nim @@ -13,7 +13,7 @@ from math import sqrt, ln, log10, log2, exp, round, arccos, arcsin, arctan, arctan2, cos, cosh, hypot, sinh, sin, tan, tanh, pow, trunc, floor, ceil, `mod` -from os import getEnv, existsEnv, dirExists, fileExists, putEnv, walkDir +from os import getEnv, existsEnv, dirExists, fileExists, putEnv, walkDir, getAppFilename template mathop(op) {.dirty.} = registerCallback(c, "stdlib.math." & astToStr(op), `op Wrapper`) @@ -120,3 +120,6 @@ proc registerAdditionalOps*(c: PCtx) = setResult(a, staticWalkDirImpl(getString(a, 0), getBool(a, 1))) systemop gorgeEx macrosop getProjectPath + + registerCallback c, "stdlib.os.getCurrentCompilerExe", proc (a: VmArgs) {.nimcall.} = + setResult(a, getAppFilename()) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 1ad276b0a..68c1e28a2 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1419,6 +1419,12 @@ type pcDir, ## path refers to a directory pcLinkToDir ## path refers to a symbolic link to a directory +proc getCurrentCompilerExe*(): string {.compileTime.} = discard + ## `getAppFilename` at CT; can be used to retrive the currently executing + ## Nim compiler from a Nim or nimscript program, or the nimble binary + ## inside a nimble program (likewise with other binaries built from + ## compiler API). + when defined(posix) and not defined(nimscript): proc getSymlinkFileKind(path: string): PathComponent = # Helper function. @@ -2118,7 +2124,8 @@ when defined(haiku): result = "" proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noNimScript.} = - ## Returns the filename of the application's executable. + ## Returns the filename of the application's executable. See also + ## `getCurrentCompilerExe`. ## ## This procedure will resolve symlinks. diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim index fc4b574e4..34daf30a9 100644 --- a/lib/system/nimscript.nim +++ b/lib/system/nimscript.nim @@ -143,6 +143,7 @@ proc existsDir*(dir: string): bool = proc selfExe*(): string = ## Returns the currently running nim or nimble executable. + # TODO: consider making this as deprecated alias of `getCurrentCompilerExe` builtin proc toExe*(filename: string): string = diff --git a/tests/vm/tgorge.nim b/tests/vm/tgorge.nim index 11c49a4cc..1f77d2c95 100644 --- a/tests/vm/tgorge.nim +++ b/tests/vm/tgorge.nim @@ -10,6 +10,8 @@ import os template getScriptDir(): string = parentDir(instantiationInfo(-1, true).filename) +# See also simpler test in Nim/tests/vm/tvmops.nim for a simpler +# cross platform way. block gorge: const execName = when defined(windows): "tgorge.bat" else: "./tgorge.sh" diff --git a/tests/vm/tvmops.nim b/tests/vm/tvmops.nim new file mode 100644 index 000000000..c9caaf32b --- /dev/null +++ b/tests/vm/tvmops.nim @@ -0,0 +1,47 @@ +#[ +test for vmops.nim +]# +import os +import math +import strutils + +template forceConst(a: untyped): untyped = + ## Force evaluation at CT, useful for example here: + ## `callFoo(forceConst(getBar1()), getBar2())` + ## instead of: + ## block: + ## const a = getBar1() + ## `callFoo(a, getBar2())` + const ret = a + ret + +static: + # TODO: add more tests + block: #getAppFilename, gorgeEx, gorge + const nim = getCurrentCompilerExe() + let ret = gorgeEx(nim & " --version") + doAssert ret.exitCode == 0 + doAssert ret.output.contains "Nim Compiler" + let ret2 = gorgeEx(nim & " --unexistant") + doAssert ret2.exitCode != 0 + let output3 = gorge(nim & " --version") + doAssert output3.contains "Nim Compiler" + + block: + const key = "D20181210T175037" + const val = "foo" + putEnv(key, val) + doAssert existsEnv(key) + doAssert getEnv(key) == val + + block: + # sanity check (we probably don't need to test for all ops) + const a1 = arcsin 0.3 + let a2 = arcsin 0.3 + doAssert a1 == a2 + +block: + # Check against bugs like #9176 + doAssert getCurrentCompilerExe() == forceConst(getCurrentCompilerExe()) + if false: #pending #9176 + doAssert gorgeEx("unexistant") == forceConst(gorgeEx("unexistant")) -- cgit 1.4.1-2-gfad0 From e1098fa01af399608f8cca72e033463c3bc13b83 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 18 Dec 2018 00:20:09 -0800 Subject: document NIM_EXTERNC for `emit` (#10022) --- doc/manual.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/manual.rst b/doc/manual.rst index a646b7963..4a1ad9d5e 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -7365,6 +7365,17 @@ Example: embedsC() +``nimbase.h`` defines ``NIM_EXTERNC`` C macro that can be used for +``extern "C"`` code to work with both ``nim c`` and ``nim cpp``, eg: + +.. code-block:: Nim + proc foobar() {.importc:"$1".} + {.emit: """ + #include + NIM_EXTERNC + void fun(){} + """.} + For backwards compatibility, if the argument to the ``emit`` statement is a single string literal, Nim symbols can be referred to via backticks. This usage is however deprecated. -- cgit 1.4.1-2-gfad0 From dd5812546f8a3dd2e65fd65ee3c786614cebc5f1 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 18 Dec 2018 00:28:26 -0800 Subject: [pathutils] add AnyPath; add `$`; lift arbitrary API restrictions (#10021) --- compiler/pathutils.nim | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/compiler/pathutils.nim b/compiler/pathutils.nim index 80c479898..7417845c0 100644 --- a/compiler/pathutils.nim +++ b/compiler/pathutils.nim @@ -17,11 +17,9 @@ type AbsoluteDir* = distinct string RelativeFile* = distinct string RelativeDir* = distinct string + AnyPath* = AbsoluteFile|AbsoluteDir|RelativeFile|RelativeDir -proc isEmpty*(x: AbsoluteFile): bool {.inline.} = x.string.len == 0 -proc isEmpty*(x: AbsoluteDir): bool {.inline.} = x.string.len == 0 -proc isEmpty*(x: RelativeFile): bool {.inline.} = x.string.len == 0 -proc isEmpty*(x: RelativeDir): bool {.inline.} = x.string.len == 0 +proc isEmpty*(x: AnyPath): bool {.inline.} = x.string.len == 0 proc copyFile*(source, dest: AbsoluteFile) = os.copyFile(source.string, dest.string) @@ -44,14 +42,13 @@ proc cmpPaths*(x, y: AbsoluteDir): int {.borrow.} proc createDir*(x: AbsoluteDir) {.borrow.} +proc `$`*(x: AnyPath): string = x.string + when true: proc eqImpl(x, y: string): bool {.inline.} = result = cmpPaths(x, y) == 0 - proc `==`*(x, y: AbsoluteFile): bool = eqImpl(x.string, y.string) - proc `==`*(x, y: AbsoluteDir): bool = eqImpl(x.string, y.string) - proc `==`*(x, y: RelativeFile): bool = eqImpl(x.string, y.string) - proc `==`*(x, y: RelativeDir): bool = eqImpl(x.string, y.string) + proc `==`*[T: AnyPath](x, y: T): bool = eqImpl(x.string, y.string) proc `/`*(base: AbsoluteDir; f: RelativeFile): AbsoluteFile = #assert isAbsolute(base.string) @@ -88,6 +85,12 @@ when true: when isMainModule: doAssert AbsoluteDir"/Users/me///" / RelativeFile"z.nim" == AbsoluteFile"/Users/me/z.nim" doAssert relativePath("/foo/bar.nim", "/foo/", '/') == "bar.nim" + doAssert $RelativeDir"foo/bar" == "foo/bar" + doAssert RelativeDir"foo/bar" == RelativeDir"foo/bar" + doAssert RelativeFile"foo/bar".changeFileExt(".txt") == RelativeFile"foo/bar.txt" + doAssert RelativeFile"foo/bar".addFileExt(".txt") == RelativeFile"foo/bar.txt" + doAssert not RelativeDir"foo/bar".isEmpty + doAssert RelativeDir"".isEmpty when isMainModule and defined(windows): let nasty = string(AbsoluteDir(r"C:\Users\rumpf\projects\nim\tests\nimble\nimbleDir\linkedPkgs\pkgB-#head\../../simplePkgs/pkgB-#head/") / RelativeFile"pkgA/module.nim") -- cgit 1.4.1-2-gfad0 From 25d3539da7b1e6fd4560efcee6037df34a9c549a Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 18 Dec 2018 03:43:25 -0800 Subject: [os] fix #10017 regression, fix #10025 regression (#10018) * [os] fix #10017 regression * [os] fix #10025 regression --- lib/pure/pathnorm.nim | 3 ++- tests/stdlib/tos.nim | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/pure/pathnorm.nim b/lib/pure/pathnorm.nim index 4a7d74bf8..ca869fd03 100644 --- a/lib/pure/pathnorm.nim +++ b/lib/pure/pathnorm.nim @@ -86,6 +86,7 @@ proc addNormalizePath*(x: string; result: var string; state: var int; dirSep = D result.add dirSep result.add substr(x, b[0], b[1]) inc state, 2 + if result == "" and x != "": result = "." proc normalizePath*(path: string; dirSep = DirSep): string = ## Example: @@ -96,7 +97,7 @@ proc normalizePath*(path: string; dirSep = DirSep): string = ## ## - Turns multiple slashes into single slashes. ## - Resolves '/foo/../bar' to '/bar'. - ## - Removes './' from the path. + ## - Removes './' from the path (but "foo/.." becomes ".") result = newStringOfCap(path.len) var state = 0 addNormalizePath(path, result, state, dirSep) diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim index 66ca3de33..ed3737844 100644 --- a/tests/stdlib/tos.nim +++ b/tests/stdlib/tos.nim @@ -190,14 +190,17 @@ block walkDirRec: removeDir("walkdir_test") block normalizedPath: + doAssert normalizedPath("") == "" block relative: - doAssert normalizedPath(".") == "" + doAssert normalizedPath(".") == "." + doAssert normalizedPath("foo/..") == "." + doAssert normalizedPath("foo//../bar/.") == "bar" doAssert normalizedPath("..") == ".." doAssert normalizedPath("../") == ".." doAssert normalizedPath("../..") == unixToNativePath"../.." doAssert normalizedPath("../a/..") == ".." doAssert normalizedPath("../a/../") == ".." - doAssert normalizedPath("./") == "" + doAssert normalizedPath("./") == "." block absolute: doAssert normalizedPath("/") == unixToNativePath"/" -- cgit 1.4.1-2-gfad0 From 8f4befe3680b1d2ce38d9b5e482a5436b49f122f Mon Sep 17 00:00:00 2001 From: alaviss Date: Wed, 19 Dec 2018 01:49:43 +0700 Subject: Testament fixes (#10034) * testament/htmlgen: add support for disabled and joined tests * testament/tester: respect --targets --- testament/htmlgen.nim | 7 ++++--- testament/tester.nim | 8 ++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/testament/htmlgen.nim b/testament/htmlgen.nim index 4a10fe00c..34902f71e 100644 --- a/testament/htmlgen.nim +++ b/testament/htmlgen.nim @@ -35,12 +35,12 @@ proc generateTestResultPanelPartial(outfile: File, testResultRow: JsonNode) = bgCtxClass = "success" resultSign = "ok" resultDescription = "PASS" - of "reIgnored": + of "reDisabled", "reJoined": panelCtxClass = "info" textCtxClass = "info" bgCtxClass = "info" resultSign = "question" - resultDescription = "SKIP" + resultDescription = if result != "reJoined": "SKIP" else: "JOINED" else: panelCtxClass = "danger" textCtxClass = "danger" @@ -78,7 +78,8 @@ proc allTestResults(onlyFailing = false): AllTests = let state = elem["result"].str inc result.totalCount if state.contains("reSuccess"): inc result.successCount - elif state.contains("reIgnored"): inc result.ignoredCount + elif state.contains("reDisabled") or state.contains("reJoined"): + inc result.ignoredCount if not onlyFailing or not(state.contains("reSuccess")): result.data.add elem result.successPercentage = 100 * diff --git a/testament/tester.nim b/testament/tester.nim index a056f36d2..1f3b96059 100644 --- a/testament/tester.nim +++ b/testament/tester.nim @@ -68,7 +68,7 @@ let pegSuccess = peg"'Hint: operation successful'.*" pegOfInterest = pegLineError / pegOtherError -var targets = {low(TTarget)..high(TTarget)} +var gTargets = {low(TTarget)..high(TTarget)} proc normalizeMsg(s: string): string = result = newStringOfCap(s.len+1) @@ -372,6 +372,10 @@ proc testSpec(r: var TResults, test: TTest, targets: set[TTarget] = {}) = expected.targets = {targetC} for target in expected.targets: inc(r.total) + if target notin gTargets: + r.addResult(test, target, "", "", reDisabled) + inc(r.skipped) + continue case expected.action of actionCompile: var given = callCompiler(expected.getCmd, test.name, test.options, target, @@ -504,7 +508,7 @@ proc main() = of "pedantic": discard "now always enabled" of "targets": targetsStr = p.val.string - targets = parseTargets(targetsStr) + gTargets = parseTargets(targetsStr) of "nim": compilerPrefix = addFileExt(p.val.string, ExeExt) of "directory": -- cgit 1.4.1-2-gfad0 From 2acefee0449b440b109aad9e5c263305dcd10f96 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Wed, 19 Dec 2018 00:21:14 +0530 Subject: Nimsuggest now shows gotodef for y in `from x import y` (#10023) --- compiler/importer.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/importer.nim b/compiler/importer.nim index 131b1ad8a..118d26d80 100644 --- a/compiler/importer.nim +++ b/compiler/importer.nim @@ -92,6 +92,7 @@ proc importSymbol(c: PContext, n: PNode, fromMod: PSym) = rawImportSymbol(c, e) e = nextIdentIter(it, fromMod.tab) else: rawImportSymbol(c, s) + suggestSym(c.config, n.info, s, c.graph.usageSym, false) proc importAllSymbolsExcept(c: PContext, fromMod: PSym, exceptSet: IntSet) = var i: TTabIter -- cgit 1.4.1-2-gfad0 From 264627a6575797a52fc108599f7295b8a2aadb37 Mon Sep 17 00:00:00 2001 From: Yuriy Glukhov Date: Tue, 18 Dec 2018 23:31:01 +0200 Subject: Add nimsuggest to installDirs (#10035) --- compiler.nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler.nimble b/compiler.nimble index 5929df3ec..f4da45519 100644 --- a/compiler.nimble +++ b/compiler.nimble @@ -4,6 +4,6 @@ author = "Andreas Rumpf" description = "Compiler package providing the compiler sources as a library." license = "MIT" -installDirs = @["compiler"] +installDirs = @["compiler", "nimsuggest"] requires "nim >= 0.14.0" -- cgit 1.4.1-2-gfad0 From 7b9ba71b47efb5fa0a3d4ea852bf11c57ca0be04 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 18 Dec 2018 22:39:08 -0800 Subject: Update osproc.nim --- lib/pure/osproc.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index b2239b9c5..85460bd67 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -229,7 +229,7 @@ proc execProcesses*(cmds: openArray[string], {.rtl, extern: "nosp$1", tags: [ExecIOEffect, TimeEffect, ReadEnvEffect, RootEffect]} = ## executes the commands `cmds` in parallel. Creates `n` processes - ## that execute in parallel. The highest return value of all processes + ## that execute in parallel. The highest return absolute value of all processes ## is returned. Runs `beforeRunEvent` before running each command. assert n > 0 -- cgit 1.4.1-2-gfad0 From 2d404a5a2425e4ba3328d50250b5dc5415fa0c04 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 18 Dec 2018 22:42:19 -0800 Subject: undo last commit (meant to send a PR instead) --- lib/pure/osproc.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 85460bd67..b2239b9c5 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -229,7 +229,7 @@ proc execProcesses*(cmds: openArray[string], {.rtl, extern: "nosp$1", tags: [ExecIOEffect, TimeEffect, ReadEnvEffect, RootEffect]} = ## executes the commands `cmds` in parallel. Creates `n` processes - ## that execute in parallel. The highest return absolute value of all processes + ## that execute in parallel. The highest return value of all processes ## is returned. Runs `beforeRunEvent` before running each command. assert n > 0 -- cgit 1.4.1-2-gfad0 From 642ce799ecde26f79984e32a9337ec1bf1c2e751 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Wed, 19 Dec 2018 14:05:25 +0530 Subject: Add support for clang-cl (#9977) --- compiler/extccomp.nim | 10 +++++++++- compiler/options.nim | 2 +- config/nim.cfg | 5 +++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index 69705db43..ef371d5d0 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -153,6 +153,13 @@ compiler vcc: structStmtFmt: "$3$n$1 $2", props: {hasCpp, hasAssume, hasDeclspec}) +compiler clangcl: + result = vcc() + result.name = "clang_cl" + result.compilerExe = "clang-cl" + result.cppCompiler = "clang-cl" + result.linkerExe = "clang-cl" + # Intel C/C++ Compiler compiler icl: result = vcc() @@ -353,7 +360,8 @@ const pcc(), ucc(), icl(), - icc()] + icc(), + clangcl()] hExt* = ".h" diff --git a/compiler/options.nim b/compiler/options.nim index 7569598d0..49d2f7404 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -132,7 +132,7 @@ type TSystemCC* = enum ccNone, ccGcc, ccNintendoSwitch, ccLLVM_Gcc, ccCLang, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc, - ccTcc, ccPcc, ccUcc, ccIcl, ccIcc + ccTcc, ccPcc, ccUcc, ccIcl, ccIcc, ccClangCl CfileFlag* {.pure.} = enum Cached, ## no need to recompile this time diff --git a/config/nim.cfg b/config/nim.cfg index e6175784e..b52c71c94 100644 --- a/config/nim.cfg +++ b/config/nim.cfg @@ -226,6 +226,11 @@ clang.options.speed = "-O3" clang.options.size = "-Os" @if windows: + clang_cl.cpp.options.always %= "${clang_cl.options.always} /EHsc" + @if not release: + clang_cl.options.linker = "/Z7" + clang_cl.cpp.options.linker = "/Z7" + @end clang.options.debug = "-g -gcodeview" clang.cpp.options.debug = "-g -gcodeview" @if not release: -- cgit 1.4.1-2-gfad0 From 48437d0a840a0be805dd19e842c39b2ca43f6712 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Wed, 19 Dec 2018 14:32:20 +0530 Subject: Give better error message when profiler and memProfiler used at same time (#9948) * Give better error message when profiler and memProfiler used at same time * Move to profiler.nim --- lib/system/profiler.nim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/system/profiler.nim b/lib/system/profiler.nim index 57b8af42d..0649f1176 100644 --- a/lib/system/profiler.nim +++ b/lib/system/profiler.nim @@ -13,6 +13,9 @@ # (except perhaps loops that have no side-effects). At every Nth call a # stack trace is taken. A stack tace is a list of cstrings. +when defined(profiler) and defined(memProfiler): + {.error: "profiler and memProfiler cannot be defined at the same time (See Embedded Stack Trace Profiler (ESTP) User Guide) for more details".} + {.push profiler: off.} const -- cgit 1.4.1-2-gfad0 From 8e90ed06188ca27932028703b7f31640cdbfa99a Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Wed, 19 Dec 2018 15:22:41 +0530 Subject: Const tuple unpacking (#9964) * tuple unpacking is now supported for consts * Move nkConstTuple to the end of TNodeKind * Add nnkConstTuple in macros.nim * Fix Formatting --- compiler/ast.nim | 3 ++- compiler/parser.nim | 40 ++++++++++++++++++++++++++-------- compiler/semstmts.nim | 60 +++++++++++++++++++++++++++++++++++---------------- lib/core/macros.nim | 3 ++- 4 files changed, 76 insertions(+), 30 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index 5f5f296cb..9ac27cb57 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -222,7 +222,8 @@ type nkState, # give a label to a code section (for iterators) nkBreakState, # special break statement for easier code generation nkFuncDef, # a func - nkTupleConstr # a tuple constructor + nkTupleConstr, # a tuple constructor + nkConstTuple # a ``const (a, b) = expr`` construct TNodeKinds* = set[TNodeKind] diff --git a/compiler/parser.nim b/compiler/parser.nim index 54b360a24..01da560c2 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -1764,21 +1764,43 @@ proc parseSection(p: var TParser, kind: TNodeKind, else: parMessage(p, errIdentifierExpected, p.tok) -proc parseConstant(p: var TParser): PNode = - #| constant = identWithPragma (colon typeDesc)? '=' optInd expr indAndComment - result = newNodeP(nkConstDef, p) - addSon(result, identWithPragma(p)) - if p.tok.tokType == tkColon: +proc parseConstTuple(p: var TParser): PNode = + result = newNodeP(nkConstTuple, p) + getTok(p) # skip '(' + optInd(p, result) + while p.tok.tokType in {tkSymbol, tkAccent}: + addSon(result, identWithPragma(p)) + if p.tok.tokType == tkColon: + getTok(p) + optInd(p, result) + addSon(result, parseTypeDesc(p)) + if p.tok.tokType != tkComma: break getTok(p) - optInd(p, result) - addSon(result, parseTypeDesc(p)) - else: - addSon(result, p.emptyNode) + + addSon(result, p.emptyNode) + eat(p, tkParRi) eat(p, tkEquals) optInd(p, result) addSon(result, parseExpr(p)) indAndComment(p, result) +proc parseConstant(p: var TParser): PNode = + #| constant = identWithPragma (colon typeDesc)? '=' optInd expr indAndComment + if p.tok.tokType == tkParLe: result = parseConstTuple(p) + else: + result = newNodeP(nkConstDef, p) + addSon(result, identWithPragma(p)) + if p.tok.tokType == tkColon: + getTok(p) + optInd(p, result) + addSon(result, parseTypeDesc(p)) + else: + addSon(result, p.emptyNode) + eat(p, tkEquals) + optInd(p, result) + addSon(result, parseExpr(p)) + indAndComment(p, result) + proc parseEnum(p: var TParser): PNode = #| enum = 'enum' optInd (symbol optInd ('=' optInd expr COMMENT?)? comma?)+ result = newNodeP(nkEnumTy, p) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 4d6c6dfb0..9bc5fa432 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -542,17 +542,17 @@ proc semConst(c: PContext, n: PNode): PNode = var a = n.sons[i] if c.config.cmd == cmdIdeTools: suggestStmt(c, a) if a.kind == nkCommentStmt: continue - if a.kind != nkConstDef: illFormedAst(a, c.config) - checkSonsLen(a, 3, c.config) - var v = semIdentDef(c, a.sons[0], skConst) - styleCheckDef(c.config, v) - onDef(a[0].info, v) + if a.kind notin {nkConstDef, nkConstTuple}: illFormedAst(a, c.config) + checkMinSonsLen(a, 3, c.config) + var length = sonsLen(a) + var typ: PType = nil - if a.sons[1].kind != nkEmpty: typ = semTypeNode(c, a.sons[1], nil) + if a.sons[length-2].kind != nkEmpty: + typ = semTypeNode(c, a.sons[length-2], nil) - var def = semConstExpr(c, a.sons[2]) + var def = semConstExpr(c, a.sons[length-1]) if def == nil: - localError(c.config, a.sons[2].info, errConstExprExpected) + localError(c.config, a.sons[length-1].info, errConstExprExpected) continue # check type compatibility between def.typ and typ: if typ != nil: @@ -560,21 +560,43 @@ proc semConst(c: PContext, n: PNode): PNode = else: typ = def.typ if typ == nil: - localError(c.config, a.sons[2].info, errConstExprExpected) + localError(c.config, a.sons[length-1].info, errConstExprExpected) continue if typeAllowed(typ, skConst) != nil and def.kind != nkNilLit: localError(c.config, a.info, "invalid type for const: " & typeToString(typ)) continue - setVarType(c, v, typ) - v.ast = def # no need to copy - if sfGenSym notin v.flags: addInterfaceDecl(c, v) - elif v.owner == nil: v.owner = getCurrOwner(c) - var b = newNodeI(nkConstDef, a.info) - if importantComments(c.config): b.comment = a.comment - addSon(b, newSymNode(v)) - addSon(b, a.sons[1]) - addSon(b, copyTree(def)) - addSon(result, b) + + var b: PNode + if a.kind == nkConstTuple: + if typ.kind != tyTuple: + localError(c.config, a.info, errXExpected, "tuple") + elif int(length/2) != sonsLen(typ): + localError(c.config, a.info, errWrongNumberOfVariables) + b = newNodeI(nkConstTuple, a.info) + newSons(b, length) + b.sons[length-2] = a.sons[length-2] + b.sons[length-1] = def + + for j in countup(0, length-3): + var v = semIdentDef(c, a.sons[j], skConst) + if sfGenSym notin v.flags: addInterfaceDecl(c, v) + elif v.owner == nil: v.owner = getCurrOwner(c) + styleCheckDef(c.config, v) + onDef(a[j].info, v) + + if a.kind != nkConstTuple: + setVarType(c, v, typ) + v.ast = def # no need to copy + b = newNodeI(nkConstDef, a.info) + if importantComments(c.config): b.comment = a.comment + addSon(b, newSymNode(v)) + addSon(b, a.sons[1]) + addSon(b, copyTree(def)) + else: + setVarType(c, v, typ.sons[j]) + v.ast = def[j] + b.sons[j] = newSymNode(v) + addSon(result,b) include semfields diff --git a/lib/core/macros.nim b/lib/core/macros.nim index f45ca3f82..7f0bda080 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -81,7 +81,8 @@ type nnkState, nnkBreakState, nnkFuncDef, - nnkTupleConstr + nnkTupleConstr, + nnkConstTuple NimNodeKinds* = set[NimNodeKind] NimTypeKind* = enum # some types are no longer used, see ast.nim -- cgit 1.4.1-2-gfad0 From 3300c8a500d0b730507180746278ecbd2a1ac1d0 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Wed, 19 Dec 2018 11:11:33 +0100 Subject: much simpler implementation of constant tuple declarations --- compiler/ast.nim | 3 +-- compiler/parser.nim | 54 ++++++++++++++++----------------------------------- compiler/semstmts.nim | 24 +++++++++++------------ lib/core/macros.nim | 7 +++---- 4 files changed, 33 insertions(+), 55 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index 9ac27cb57..5f5f296cb 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -222,8 +222,7 @@ type nkState, # give a label to a code section (for iterators) nkBreakState, # special break statement for easier code generation nkFuncDef, # a func - nkTupleConstr, # a tuple constructor - nkConstTuple # a ``const (a, b) = expr`` construct + nkTupleConstr # a tuple constructor TNodeKinds* = set[TNodeKind] diff --git a/compiler/parser.nim b/compiler/parser.nim index 01da560c2..e26ea5ee2 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -1764,43 +1764,6 @@ proc parseSection(p: var TParser, kind: TNodeKind, else: parMessage(p, errIdentifierExpected, p.tok) -proc parseConstTuple(p: var TParser): PNode = - result = newNodeP(nkConstTuple, p) - getTok(p) # skip '(' - optInd(p, result) - while p.tok.tokType in {tkSymbol, tkAccent}: - addSon(result, identWithPragma(p)) - if p.tok.tokType == tkColon: - getTok(p) - optInd(p, result) - addSon(result, parseTypeDesc(p)) - if p.tok.tokType != tkComma: break - getTok(p) - - addSon(result, p.emptyNode) - eat(p, tkParRi) - eat(p, tkEquals) - optInd(p, result) - addSon(result, parseExpr(p)) - indAndComment(p, result) - -proc parseConstant(p: var TParser): PNode = - #| constant = identWithPragma (colon typeDesc)? '=' optInd expr indAndComment - if p.tok.tokType == tkParLe: result = parseConstTuple(p) - else: - result = newNodeP(nkConstDef, p) - addSon(result, identWithPragma(p)) - if p.tok.tokType == tkColon: - getTok(p) - optInd(p, result) - addSon(result, parseTypeDesc(p)) - else: - addSon(result, p.emptyNode) - eat(p, tkEquals) - optInd(p, result) - addSon(result, parseExpr(p)) - indAndComment(p, result) - proc parseEnum(p: var TParser): PNode = #| enum = 'enum' optInd (symbol optInd ('=' optInd expr COMMENT?)? comma?)+ result = newNodeP(nkEnumTy, p) @@ -2057,6 +2020,23 @@ proc parseVariable(p: var TParser): PNode = result[^1] = postExprBlocks(p, result[^1]) indAndComment(p, result) +proc parseConstant(p: var TParser): PNode = + #| constant = (parseVarTuple / identWithPragma) (colon typeDesc)? '=' optInd expr indAndComment + if p.tok.tokType == tkParLe: result = parseVarTuple(p) + else: + result = newNodeP(nkConstDef, p) + addSon(result, identWithPragma(p)) + if p.tok.tokType == tkColon: + getTok(p) + optInd(p, result) + addSon(result, parseTypeDesc(p)) + else: + addSon(result, p.emptyNode) + eat(p, tkEquals) + optInd(p, result) + addSon(result, parseExpr(p)) + indAndComment(p, result) + proc parseBind(p: var TParser, k: TNodeKind): PNode = #| bindStmt = 'bind' optInd qualifiedIdent ^+ comma #| mixinStmt = 'mixin' optInd qualifiedIdent ^+ comma diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 9bc5fa432..4b714e1f3 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -542,7 +542,7 @@ proc semConst(c: PContext, n: PNode): PNode = var a = n.sons[i] if c.config.cmd == cmdIdeTools: suggestStmt(c, a) if a.kind == nkCommentStmt: continue - if a.kind notin {nkConstDef, nkConstTuple}: illFormedAst(a, c.config) + if a.kind notin {nkConstDef, nkVarTuple}: illFormedAst(a, c.config) checkMinSonsLen(a, 3, c.config) var length = sonsLen(a) @@ -567,16 +567,16 @@ proc semConst(c: PContext, n: PNode): PNode = continue var b: PNode - if a.kind == nkConstTuple: + if a.kind == nkVarTuple: if typ.kind != tyTuple: localError(c.config, a.info, errXExpected, "tuple") elif int(length/2) != sonsLen(typ): localError(c.config, a.info, errWrongNumberOfVariables) - b = newNodeI(nkConstTuple, a.info) + b = newNodeI(nkVarTuple, a.info) newSons(b, length) b.sons[length-2] = a.sons[length-2] b.sons[length-1] = def - + for j in countup(0, length-3): var v = semIdentDef(c, a.sons[j], skConst) if sfGenSym notin v.flags: addInterfaceDecl(c, v) @@ -584,14 +584,14 @@ proc semConst(c: PContext, n: PNode): PNode = styleCheckDef(c.config, v) onDef(a[j].info, v) - if a.kind != nkConstTuple: - setVarType(c, v, typ) - v.ast = def # no need to copy - b = newNodeI(nkConstDef, a.info) - if importantComments(c.config): b.comment = a.comment - addSon(b, newSymNode(v)) - addSon(b, a.sons[1]) - addSon(b, copyTree(def)) + if a.kind != nkVarTuple: + setVarType(c, v, typ) + v.ast = def # no need to copy + b = newNodeI(nkConstDef, a.info) + if importantComments(c.config): b.comment = a.comment + addSon(b, newSymNode(v)) + addSon(b, a.sons[1]) + addSon(b, copyTree(def)) else: setVarType(c, v, typ.sons[j]) v.ast = def[j] diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 7f0bda080..291858bed 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -81,8 +81,7 @@ type nnkState, nnkBreakState, nnkFuncDef, - nnkTupleConstr, - nnkConstTuple + nnkTupleConstr NimNodeKinds* = set[NimNodeKind] NimTypeKind* = enum # some types are no longer used, see ast.nim @@ -278,9 +277,9 @@ when defined(nimHasSymOwnerInMacro): when defined(nimHasInstantiationOfInMacro): proc isInstantiationOf*(instanceProcSym, genProcSym: NimNode): bool {.magic: "SymIsInstantiationOf", noSideEffect.} ## check if proc symbol is instance of the generic proc symbol - ## useful to check proc symbols against generic symbols + ## useful to check proc symbols against generic symbols ## returned by `bindSym` - + proc getType*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.} ## with 'getType' you can access the node's `type`:idx:. A Nim type is ## mapped to a Nim AST too, so it's slightly confusing but it means the same -- cgit 1.4.1-2-gfad0 From 37cb5e8f532be0ffe82400d297b80ea9dd90494a Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 19 Dec 2018 02:25:50 -0800 Subject: Update osproc.nim (#10043) --- lib/pure/osproc.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index b2239b9c5..72581f47c 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -229,7 +229,7 @@ proc execProcesses*(cmds: openArray[string], {.rtl, extern: "nosp$1", tags: [ExecIOEffect, TimeEffect, ReadEnvEffect, RootEffect]} = ## executes the commands `cmds` in parallel. Creates `n` processes - ## that execute in parallel. The highest return value of all processes + ## that execute in parallel. The highest (absolute) return value of all processes ## is returned. Runs `beforeRunEvent` before running each command. assert n > 0 -- cgit 1.4.1-2-gfad0 From 730b1b2b878f18a3bee71dc1dcd463891213cd83 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Wed, 19 Dec 2018 20:48:57 +0530 Subject: proc does not take untyped/typed as argument (#9981) * proc does not take untyped/typed as argument * Add TODO --- compiler/semtypes.nim | 5 +++++ tests/proc/typed.nim | 7 +++++++ tests/proc/untyped.nim | 7 +++++++ 3 files changed, 19 insertions(+) create mode 100644 tests/proc/typed.nim create mode 100644 tests/proc/untyped.nim diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index dc27e5823..e0f04c22f 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1064,6 +1064,11 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, if hasType: typ = semParamType(c, a.sons[length-2], constraint) + var owner = getCurrOwner(c).owner + # TODO: Disallow typed/untyped in procs in the compiler/stdlib + if (owner.kind != skModule or owner.owner.name.s != "stdlib") and + kind == skProc and (typ.kind == tyStmt or typ.kind == tyExpr): + localError(c.config, a.sons[length-2].info, "'" & typ.sym.name.s & "' is only allowed in templates and macros") if hasDefault: def = a[^1] diff --git a/tests/proc/typed.nim b/tests/proc/typed.nim new file mode 100644 index 000000000..2e8117634 --- /dev/null +++ b/tests/proc/typed.nim @@ -0,0 +1,7 @@ +discard """ + errormsg: "'typed' is only allowed in templates and macros" + line: 6 +""" + +proc fun(x:typed)=discard +fun(10) diff --git a/tests/proc/untyped.nim b/tests/proc/untyped.nim new file mode 100644 index 000000000..f8b3ead7b --- /dev/null +++ b/tests/proc/untyped.nim @@ -0,0 +1,7 @@ +discard """ + errormsg: "'untyped' is only allowed in templates and macros" + line: 6 +""" + +proc fun(x:untyped)=discard +fun(10) -- cgit 1.4.1-2-gfad0 From f7d0e5c1d1cfdf3b00ea464dc30c9fe96dcfad95 Mon Sep 17 00:00:00 2001 From: Araq Date: Wed, 19 Dec 2018 18:06:13 +0100 Subject: strutils: minor code cleanup --- lib/pure/strutils.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 4d0fe800e..00469f9e5 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1448,7 +1448,6 @@ proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last = 0): int let last = if last==0: s.high else: last - sLen = last - start + 1 subLast = sub.len - 1 if subLast == -1: -- cgit 1.4.1-2-gfad0 From c129107b32528a39d82a0459f8c22ab68fc70a43 Mon Sep 17 00:00:00 2001 From: Araq Date: Wed, 19 Dec 2018 18:06:28 +0100 Subject: make nimrtl.dll compile for the C++ target --- compiler/ccgtypes.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 266f63647..2d1b884c0 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -544,7 +544,7 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope, appcg(m, result, "virtual void raise() {throw *this;}$n") # required for polymorphic exceptions if typ.sym.magic == mException: # Add cleanup destructor to Exception base class - appcg(m, result, "~$1() {if(this->raiseId) popCurrentExceptionEx(this->raiseId);}$n", [name]) + appcg(m, result, "~$1() {if(this->raiseId) #popCurrentExceptionEx(this->raiseId);}$n", [name]) # hack: forward declare popCurrentExceptionEx() on top of type description, # proper request to generate popCurrentExceptionEx not possible for 2 reasons: # generated function will be below declared Exception type and circular dependency -- cgit 1.4.1-2-gfad0 From 656770402c85227c0597f260f593834b1bd2d3f5 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 19 Dec 2018 04:55:51 -0800 Subject: fix #8255 numerous issues with splitFile --- lib/pure/os.nim | 17 +++++++++++++---- tests/stdlib/tos.nim | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 68c1e28a2..46e4e7b69 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -349,19 +349,28 @@ proc splitFile*(path: string): tuple[dir, name, ext: string] {. ## If `path` has no extension, `ext` is the empty string. ## If `path` has no directory component, `dir` is the empty string. ## If `path` has no filename component, `name` and `ext` are empty strings. - if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: - result = (path, "", "") + if path.len == 0 or path[^1] in {DirSep, AltSep}: + if path.len == 1: + # issue #8255 + result = (path[0 .. ^1], "", "") + else: + result = (path[0 ..< ^1], "", "") else: var sepPos = -1 var dotPos = path.len for i in countdown(len(path)-1, 0): if path[i] == ExtSep: if dotPos == path.len and i > 0 and - path[i-1] notin {DirSep, AltSep}: dotPos = i + path[i-1] notin {DirSep, AltSep, ExtSep}: dotPos = i elif path[i] in {DirSep, AltSep}: sepPos = i break - result.dir = substr(path, 0, sepPos-1) + if sepPos-1 >= 0: + result.dir = substr(path, 0, sepPos-1) + elif path[0] in {DirSep, AltSep}: + # issue #8255 + result.dir = $path[0] + result.name = substr(path, sepPos+1, dotPos-1) result.ext = substr(path, dotPos) diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim index ed3737844..e49ab2506 100644 --- a/tests/stdlib/tos.nim +++ b/tests/stdlib/tos.nim @@ -239,3 +239,18 @@ block absolutePath: doAssert absolutePath("a", "/b/c") == "/b/c" / "a" doAssert absolutePath("/a", "b/") == "/a" +block splitFile: + doAssert splitFile("abc/") == ("abc", "", "") + doAssert splitFile("/") == ("/", "", "") + doAssert splitFile("./abc") == (".", "abc", "") + doAssert splitFile(".txt") == ("", ".txt", "") + doAssert splitFile("abc/.txt") == ("abc", ".txt", "") + doAssert splitFile("abc") == ("", "abc", "") + doAssert splitFile("abc.txt") == ("", "abc", ".txt") + doAssert splitFile("/abc.txt") == ("/", "abc", ".txt") + doAssert splitFile("/foo/abc.txt") == ("/foo", "abc", ".txt") + doAssert splitFile("/foo/abc.txt.gz") == ("/foo", "abc.txt", ".gz") + doAssert splitFile(".") == ("", ".", "") + doAssert splitFile("abc/.") == ("abc", ".", "") + doAssert splitFile("..") == ("", "..", "") + doAssert splitFile("a/..") == ("a", "..", "") -- cgit 1.4.1-2-gfad0 From 8fbe3b3b7d6b6cd19a71c238034524dc64aa703b Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 19 Dec 2018 04:58:04 -0800 Subject: add to changelog --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index ecdd26086..d25b6101d 100644 --- a/changelog.md +++ b/changelog.md @@ -50,6 +50,8 @@ `IndexError` for un-named captures. This is consistant with `RegexMatch.{captureBounds,captures}[]`. +- splitFile now correctly handles edge cases, see #10047 + #### Breaking changes in the compiler - The compiler now implements the "generic symbol prepass" for `when` statements -- cgit 1.4.1-2-gfad0 From fc7df3283c11c71c60d079b5aea7deb70ffcb559 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 19 Dec 2018 12:38:50 -0800 Subject: fix test failure --- lib/pure/os.nim | 6 ++++-- tests/stdlib/tos.nim | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 46e4e7b69..2919b39f0 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -349,10 +349,12 @@ proc splitFile*(path: string): tuple[dir, name, ext: string] {. ## If `path` has no extension, `ext` is the empty string. ## If `path` has no directory component, `dir` is the empty string. ## If `path` has no filename component, `name` and `ext` are empty strings. - if path.len == 0 or path[^1] in {DirSep, AltSep}: + if path.len == 0: + result = ("", "", "") + elif path[^1] in {DirSep, AltSep}: if path.len == 1: # issue #8255 - result = (path[0 .. ^1], "", "") + result = ($path[0], "", "") else: result = (path[0 ..< ^1], "", "") else: diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim index e49ab2506..577baea87 100644 --- a/tests/stdlib/tos.nim +++ b/tests/stdlib/tos.nim @@ -240,6 +240,7 @@ block absolutePath: doAssert absolutePath("/a", "b/") == "/a" block splitFile: + doAssert splitFile("") == ("", "", "") doAssert splitFile("abc/") == ("abc", "", "") doAssert splitFile("/") == ("/", "", "") doAssert splitFile("./abc") == (".", "abc", "") -- cgit 1.4.1-2-gfad0 From 6cbe1d48d7717315e41b22a758357ceb24992fcd Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 19 Dec 2018 16:13:25 -0800 Subject: [json] document fact that key ordering is preserved (#10020) --- lib/pure/json.nim | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 389df4087..010dd8f70 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -134,6 +134,13 @@ ## j2["details"] = %* {"age":35, "pi":3.1415} ## echo j2 +runnableExamples: + ## Note: for JObject, key ordering is preserved, unlike in some languages, + ## this is convenient for some use cases. Example: + type Foo = object + a1, a2, a0, a3, a4: int + doAssert $(%* Foo()) == """{"a1":0,"a2":0,"a0":0,"a3":0,"a4":0}""" + import hashes, tables, strutils, lexbase, streams, unicode, macros, parsejson -- cgit 1.4.1-2-gfad0 From cd65e5328dc82ca74a2905641a12e2c84a82d1de Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 20 Dec 2018 08:20:32 +0100 Subject: GC: avoid pathological behaviour; fixes #10040 [backport] (#10052) --- lib/system/gc.nim | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/system/gc.nim b/lib/system/gc.nim index fb20edbbb..9ae388aa9 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -17,9 +17,9 @@ const CycleIncrease = 2 # is a multiplicative increase InitialCycleThreshold = 4*1024*1024 # X MB because cycle checking is slow - ZctThreshold = 500 # we collect garbage if the ZCT's size - # reaches this threshold - # this seems to be a good value + InitialZctThreshold = 500 # we collect garbage if the ZCT's size + # reaches this threshold + # this seems to be a good value withRealTime = defined(useRealtimeGC) when withRealTime and not declared(getTicks): @@ -78,6 +78,7 @@ type when nimCoroutines: activeStack: ptr GcStack # current executing coroutine stack. cycleThreshold: int + zctThreshold: int when useCellIds: idGenerator: int zct: CellSeq # the zero count table @@ -253,6 +254,7 @@ proc initGC() = when traceGC: for i in low(CellState)..high(CellState): init(states[i]) gch.cycleThreshold = InitialCycleThreshold + gch.zctThreshold = InitialZctThreshold gch.stat.stackScans = 0 gch.stat.cycleCollections = 0 gch.stat.maxThreshold = 0 @@ -771,11 +773,7 @@ proc collectCTBody(gch: var GcHeap) = c_fprintf(stdout, "[GC] missed deadline: %ld\n", duration) proc collectCT(gch: var GcHeap) = - # stackMarkCosts prevents some pathological behaviour: Stack marking - # becomes more expensive with large stacks and large stacks mean that - # cells with RC=0 are more likely to be kept alive by the stack. - let stackMarkCosts = max(stackSize() div (16*sizeof(int)), ZctThreshold) - if (gch.zct.len >= stackMarkCosts or (cycleGC and + if (gch.zct.len >= gch.zctThreshold or (cycleGC and getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) and gch.recGcLock == 0: when false: @@ -783,6 +781,7 @@ proc collectCT(gch: var GcHeap) = cellsetReset(gch.marked) markForDebug(gch) collectCTBody(gch) + gch.zctThreshold = max(InitialZctThreshold, gch.zct.len * CycleIncrease) when withRealTime: proc toNano(x: int): Nanos {.inline.} = @@ -793,10 +792,11 @@ when withRealTime: proc GC_step(gch: var GcHeap, us: int, strongAdvice: bool) = gch.maxPause = us.toNano - if (gch.zct.len >= ZctThreshold or (cycleGC and + if (gch.zct.len >= gch.zctThreshold or (cycleGC and getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) or strongAdvice: collectCTBody(gch) + gch.zctThreshold = max(InitialZctThreshold, gch.zct.len * CycleIncrease) proc GC_step*(us: int, strongAdvice = false, stackSize = -1) {.noinline.} = if stackSize >= 0: -- cgit 1.4.1-2-gfad0 From ca18dc2505d3e3429860bef89d7e6a353cf935de Mon Sep 17 00:00:00 2001 From: rec <44084068+recloser@users.noreply.github.com> Date: Thu, 20 Dec 2018 08:24:57 +0100 Subject: Make copies for params which are captured in closures. Fixes #7048 (#10050) * Copy params which are captured in closures. Fixes #7048 * Forgot to emit a newline; minor adjustments to the test --- compiler/jsgen.nim | 20 +++++++++++++++++++- tests/js/tclosures.nim | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index cd51aaddd..daaa145e1 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -94,6 +94,7 @@ type options: TOptions module: BModule g: PGlobals + generatedParamCopies: IntSet beforeRetNeeded: bool unique: int # for temp identifier generation blocks: seq[TBlock] @@ -924,7 +925,7 @@ const proc needsNoCopy(p: PProc; y: PNode): bool = return y.kind in nodeKindsNeedNoCopy or - (mapType(y.typ) != etyBaseIndex and + ((mapType(y.typ) != etyBaseIndex or y.sym.kind == skParam) and (skipTypes(y.typ, abstractInst).kind in {tyRef, tyPtr, tyLent, tyVar, tyCString, tyProc} + IntegralTypes)) @@ -1232,12 +1233,29 @@ proc genProcForSymIfNeeded(p: PProc, s: PSym) = if owner != nil: add(owner.locals, newp) else: attachProc(p, newp, s) +proc genCopyForParamIfNeeded(p: PProc, n: PNode) = + let s = n.sym + if p.prc == s.owner or needsNoCopy(p, n): + return + var owner = p.up + while true: + if owner == nil: + internalError(p.config, n.info, "couldn't find the owner proc of the closed over param: " & s.name.s) + if owner.prc == s.owner: + if not owner.generatedParamCopies.containsOrIncl(s.id): + let copy = "$1 = nimCopy(null, $1, $2);$n" % [s.loc.r, genTypeInfo(p, s.typ)] + add(owner.locals, owner.indentLine(copy)) + return + owner = owner.up + proc genSym(p: PProc, n: PNode, r: var TCompRes) = var s = n.sym case s.kind of skVar, skLet, skParam, skTemp, skResult, skForVar: if s.loc.r == nil: internalError(p.config, n.info, "symbol has no generated name: " & s.name.s) + if s.kind == skParam: + genCopyForParamIfNeeded(p, n) let k = mapType(p, s.typ) if k == etyBaseIndex: r.typ = etyBaseIndex diff --git a/tests/js/tclosures.nim b/tests/js/tclosures.nim index 659c60092..70037f4bf 100644 --- a/tests/js/tclosures.nim +++ b/tests/js/tclosures.nim @@ -49,3 +49,47 @@ for i in 1 .. 10: let results = runCallbacks() doAssert(expected == $results) + +block issue7048: + block: + proc foo(x: seq[int]): auto = + proc bar: int = x[1] + bar + + var stuff = @[1, 2] + let f = foo(stuff) + stuff[1] = 321 + doAssert f() == 2 + + block: + proc foo(x: tuple[things: string]; y: array[3, int]): auto = + proc very: auto = + proc deeply: auto = + proc nested: (char, int) = (x.things[0], y[1]) + nested + deeply + very() + + var + stuff = (things: "NIM") + stuff2 = [32, 64, 96] + let f = foo(stuff, stuff2) + stuff.things = "VIM" + stuff2[1] *= 10 + doAssert f()() == ('N', 64) + doAssert (stuff.things[0], stuff2[1]) == ('V', 640) + + block: + proc foo(x: ptr string): auto = + proc bar(): int = len(x[]) + bar + + var + s1 = "xyz" + s2 = "stuff" + p = addr s1 + + let f = foo(p) + p = addr s2 + doAssert len(p[]) == 5 + doAssert f() == 3 \ No newline at end of file -- cgit 1.4.1-2-gfad0 From 68ec42cec70496eef072c921f54a91686bb8b43b Mon Sep 17 00:00:00 2001 From: Taylor Hoff Date: Thu, 20 Dec 2018 02:45:43 -0500 Subject: Add call to hostnamectl in detectOsImpl (#10048) --- lib/pure/distros.nim | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pure/distros.nim b/lib/pure/distros.nim index 5847cfadb..4bf1c3da3 100644 --- a/lib/pure/distros.nim +++ b/lib/pure/distros.nim @@ -133,8 +133,8 @@ const LacksDevPackages* = {Distribution.Gentoo, Distribution.Slackware, Distribution.ArchLinux} -var unameRes, releaseRes: string ## we cache the result of the 'uname -a' - ## execution for faster platform detections. +var unameRes, releaseRes, hostnamectlRes: string ## we cache the result of the 'uname -a' + ## execution for faster platform detections. template unameRelease(cmd, cache): untyped = if cache.len == 0: @@ -143,6 +143,7 @@ template unameRelease(cmd, cache): untyped = template uname(): untyped = unameRelease("uname -a", unameRes) template release(): untyped = unameRelease("lsb_release -a", releaseRes) +template hostnamectl(): untyped = unameRelease("hostnamectl", hostnamectlRes) proc detectOsImpl(d: Distribution): bool = case d @@ -172,7 +173,7 @@ proc detectOsImpl(d: Distribution): bool = result = defined(haiku) else: let dd = toLowerAscii($d) - result = dd in toLowerAscii(uname()) or dd in toLowerAscii(release()) + result = dd in toLowerAscii(uname()) or dd in toLowerAscii(release()) or dd in toLowerAscii(hostnamectl()) template detectOs*(d: untyped): bool = ## Distro/OS detection. For convenience the -- cgit 1.4.1-2-gfad0 From 2fd522cf58e40064747e52e0eeb53f28e67d3243 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 20 Dec 2018 11:44:26 +0100 Subject: use anon structs and unions for a much better debug experience (#10055) --- compiler/ccgtypes.nim | 25 +++++++++---------------- tests/misc/tsizeof.nim | 43 ++++++++++++++++++++----------------------- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 2d1b884c0..ed4aa8001 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -436,30 +436,25 @@ proc mangleRecFieldName(m: BModule; field: PSym): Rope = if result == nil: internalError(m.config, field.info, "mangleRecFieldName") proc genRecordFieldsAux(m: BModule, n: PNode, - accessExpr: Rope, rectype: PType, + rectype: PType, check: var IntSet): Rope = result = nil case n.kind of nkRecList: for i in countup(0, sonsLen(n) - 1): - add(result, genRecordFieldsAux(m, n.sons[i], accessExpr, rectype, check)) + add(result, genRecordFieldsAux(m, n.sons[i], rectype, check)) of nkRecCase: if n.sons[0].kind != nkSym: internalError(m.config, n.info, "genRecordFieldsAux") - add(result, genRecordFieldsAux(m, n.sons[0], accessExpr, rectype, check)) + add(result, genRecordFieldsAux(m, n.sons[0], rectype, check)) # prefix mangled name with "_U" to avoid clashes with other field names, # since identifiers are not allowed to start with '_' - let uname = rope("_U" & mangle(n.sons[0].sym.name.s)) - let ae = if accessExpr != nil: "$1.$2" % [accessExpr, uname] - else: uname var unionBody: Rope = nil for i in countup(1, sonsLen(n) - 1): case n.sons[i].kind of nkOfBranch, nkElse: let k = lastSon(n.sons[i]) if k.kind != nkSym: - let sname = "S" & rope(i) - let a = genRecordFieldsAux(m, k, "$1.$2" % [ae, sname], rectype, - check) + let a = genRecordFieldsAux(m, k, rectype, check) if a != nil: if tfPacked notin rectype.flags: add(unionBody, "struct {") @@ -469,22 +464,20 @@ proc genRecordFieldsAux(m: BModule, n: PNode, else: addf(unionBody, "#pragma pack(push, 1)$nstruct{", []) add(unionBody, a) - addf(unionBody, "} $1;$n", [sname]) + addf(unionBody, "};$n", []) if tfPacked in rectype.flags and hasAttribute notin CC[m.config.cCompiler].props: addf(unionBody, "#pragma pack(pop)$n", []) else: - add(unionBody, genRecordFieldsAux(m, k, ae, rectype, check)) + add(unionBody, genRecordFieldsAux(m, k, rectype, check)) else: internalError(m.config, "genRecordFieldsAux(record case branch)") if unionBody != nil: - addf(result, "union{$n$1} $2;$n", [unionBody, uname]) + addf(result, "union{$n$1};$n", [unionBody]) of nkSym: let field = n.sym if field.typ.kind == tyVoid: return #assert(field.ast == nil) let sname = mangleRecFieldName(m, field) - let ae = if accessExpr != nil: "$1.$2" % [accessExpr, sname] - else: sname - fillLoc(field.loc, locField, n, ae, OnUnknown) + fillLoc(field.loc, locField, n, sname, OnUnknown) # for importcpp'ed objects, we only need to set field.loc, but don't # have to recurse via 'getTypeDescAux'. And not doing so prevents problems # with heavily templatized C++ code: @@ -505,7 +498,7 @@ proc genRecordFieldsAux(m: BModule, n: PNode, else: internalError(m.config, n.info, "genRecordFieldsAux()") proc getRecordFields(m: BModule, typ: PType, check: var IntSet): Rope = - result = genRecordFieldsAux(m, typ.n, nil, typ, check) + result = genRecordFieldsAux(m, typ.n, typ, check) proc fillObjectFields*(m: BModule; typ: PType) = # sometimes generic objects are not consistently merged. We patch over diff --git a/tests/misc/tsizeof.nim b/tests/misc/tsizeof.nim index f60c7fa00..a73b3dcde 100644 --- a/tests/misc/tsizeof.nim +++ b/tests/misc/tsizeof.nim @@ -36,27 +36,24 @@ macro testSizeAlignOf(args: varargs[untyped]): untyped = if nim_size != c_size or nim_align != c_align: var msg = strAlign(`arg`.type.name & ": ") if nim_size != c_size: - msg.add " size(got, expected): " & $nim_size & " != " & $c_size + msg.add " size(got, expected): " & $nim_size & " != " & $c_size if nim_align != c_align: msg.add " align(get, expected): " & $nim_align & " != " & $c_align echo msg failed = true -macro testOffsetOf(a,b1,b2: untyped): untyped = +macro testOffsetOf(a, b: untyped): untyped = let typeName = newLit(a.repr) - let member = newLit(b2.repr) + let member = newLit(b.repr) result = quote do: let - c_offset = c_offsetof(`a`,`b1`) - nim_offset = offsetof(`a`,`b2`) + c_offset = c_offsetof(`a`,`b`) + nim_offset = offsetof(`a`,`b`) if c_offset != nim_offset: echo `typeName`, ".", `member`, " offsetError, C: ", c_offset, " nim: ", nim_offset failed = true -template testOffsetOf(a,b: untyped): untyped = - testOffsetOf(a,b,b) - proc strAlign(arg: string): string = const minLen = 22 result = arg @@ -337,16 +334,16 @@ testinstance: testOffsetOf(AlignAtEnd, b) testOffsetOf(AlignAtEnd, c) - testOffsetOf(SimpleBranch, "_Ukind", a) - testOffsetOf(SimpleBranch, "_Ukind", b) - testOffsetOf(SimpleBranch, "_Ukind", c) + testOffsetOf(SimpleBranch, a) + testOffsetOf(SimpleBranch, b) + testOffsetOf(SimpleBranch, c) testOffsetOf(PaddingBeforeBranchA, cause) - testOffsetOf(PaddingBeforeBranchA, "_Ukind", a) + testOffsetOf(PaddingBeforeBranchA, a) testOffsetOf(PaddingBeforeBranchB, cause) - testOffsetOf(PaddingBeforeBranchB, "_Ukind", a) + testOffsetOf(PaddingBeforeBranchB, a) - testOffsetOf(PaddingAfterBranch, "_Ukind", a) + testOffsetOf(PaddingAfterBranch, a) testOffsetOf(PaddingAfterBranch, cause) testOffsetOf(Foobar, c) @@ -367,15 +364,15 @@ testinstance: testOffsetOf(EnumObjectB, d) testOffsetOf(RecursiveStuff, kind) - testOffsetOf(RecursiveStuff, "_Ukind.S1.a", a) - testOffsetOf(RecursiveStuff, "_Ukind.S2.b", b) - testOffsetOf(RecursiveStuff, "_Ukind.S3.kind2", kind2) - testOffsetOf(RecursiveStuff, "_Ukind.S3._Ukind2.S1.ca1", ca1) - testOffsetOf(RecursiveStuff, "_Ukind.S3._Ukind2.S1.ca2", ca2) - testOffsetOf(RecursiveStuff, "_Ukind.S3._Ukind2.S2.cb", cb) - testOffsetOf(RecursiveStuff, "_Ukind.S3._Ukind2.S3.cc", cc) - testOffsetOf(RecursiveStuff, "_Ukind.S3.d1", d1) - testOffsetOf(RecursiveStuff, "_Ukind.S3.d2", d2) + testOffsetOf(RecursiveStuff, a) + testOffsetOf(RecursiveStuff, b) + testOffsetOf(RecursiveStuff, kind2) + testOffsetOf(RecursiveStuff, ca1) + testOffsetOf(RecursiveStuff, ca2) + testOffsetOf(RecursiveStuff, cb) + testOffsetOf(RecursiveStuff, cc) + testOffsetOf(RecursiveStuff, d1) + testOffsetOf(RecursiveStuff, d2) main() -- cgit 1.4.1-2-gfad0 From f9d58b9305e256fc928dc1be059333b6b33d7154 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Thu, 20 Dec 2018 02:49:32 -0800 Subject: lots of testament bug fixes and improvements: (#10044) --- testament/categories.nim | 72 +++++++++++++++++++++++++++++++++-------- testament/tester.nim | 57 ++++++++++++++++++++++++++++---- tests/testament/tshouldfail.nim | 22 ++++++------- 3 files changed, 120 insertions(+), 31 deletions(-) diff --git a/testament/categories.nim b/testament/categories.nim index a72602217..ecaead9ef 100644 --- a/testament/categories.nim +++ b/testament/categories.nim @@ -402,14 +402,42 @@ proc compileExample(r: var TResults, pattern, options: string, cat: Category) = testSpec r, test proc testStdlib(r: var TResults, pattern, options: string, cat: Category) = - for testFile in os.walkFiles(pattern): - let name = extractFilename(testFile) - if name notin disabledFiles: - let contents = readFile(testFile).string - var testObj = makeTest(testFile, options, cat) - if "when isMainModule" notin contents: - testObj.spec.action = actionCompile - testSpec r, testObj + var files: seq[string] + + proc isValid(file: string): bool = + for dir in parentDirs(file, inclusive = false): + if dir.lastPathPart in ["includes", "nimcache"]: + # eg: lib/pure/includes/osenv.nim gives: Error: This is an include file for os.nim! + return false + let name = extractFilename(file) + if name.splitFile.ext != ".nim": return false + for namei in disabledFiles: + # because of `LockFreeHash.nim` which has case + if namei.cmpPaths(name) == 0: return false + return true + + for testFile in os.walkDirRec(pattern): + if isValid(testFile): + files.add testFile + + files.sort # reproducible order + for testFile in files: + let contents = readFile(testFile).string + var testObj = makeTest(testFile, options, cat) + #[ + TODO: + this logic is fragile: + false positives (if appears in a comment), or false negatives, eg + `when defined(osx) and isMainModule`. + Instead of fixing this, a much better way, is to extend + https://github.com/nim-lang/Nim/issues/9581 to stdlib modules as follows: + * add these to megatest + * patch compiler so `isMainModule` is true when -d:isMainModuleIsAlwaysTrue + That'll give speedup benefit, and we don't have to patch stdlib files. + ]# + if "when isMainModule" notin contents: + testObj.spec.action = actionCompile + testSpec r, testObj # ----------------------------- nimble ---------------------------------------- type @@ -517,7 +545,9 @@ proc isJoinableSpec(spec: TSpec): bool = result = not spec.sortoutput and spec.action == actionRun and not fileExists(spec.file.changeFileExt("cfg")) and + not fileExists(spec.file.changeFileExt("nims")) and not fileExists(parentDir(spec.file) / "nim.cfg") and + not fileExists(parentDir(spec.file) / "config.nims") and spec.cmd.len == 0 and spec.err != reDisabled and not spec.unjoinable and @@ -535,21 +565,31 @@ proc norm(s: var string) = s = tmp s = s.strip +proc isTestFile*(file: string): bool = + let (dir, name, ext) = splitFile(file) + result = ext == ".nim" and name.startsWith("t") + proc runJoinedTest(r: var TResults, cat: Category, testsDir: string) = ## returs a list of tests that have problems var specs: seq[TSpec] = @[] - for kind, dir in walkDir(testsDir): assert testsDir.startsWith(testsDir) let cat = dir[testsDir.len .. ^1] if kind == pcDir and cat notin specialCategories: - for file in os.walkFiles(testsDir / cat / "t*.nim"): + for file in walkDirRec(testsDir / cat): + if not isTestFile(file): continue let spec = parseSpec(file) if isJoinableSpec(spec): specs.add spec echo "joinable specs: ", specs.len + if simulate: + var s = "runJoinedTest: " + for a in specs: s.add a.file & " " + echo s + return + var megatest: string for runSpec in specs: megatest.add "import r\"" @@ -623,8 +663,8 @@ proc processCategory(r: var TResults, cat: Category, options, testsDir: string, of "async": asyncTests r, cat, options of "lib": - testStdlib(r, "lib/pure/*.nim", options, cat) - testStdlib(r, "lib/packages/docutils/highlite", options, cat) + testStdlib(r, "lib/pure/", options, cat) + testStdlib(r, "lib/packages/docutils/", options, cat) of "examples": compileExample(r, "examples/*.nim", options, cat) compileExample(r, "examples/gtk/*.nim", options, cat) @@ -644,7 +684,13 @@ proc processCategory(r: var TResults, cat: Category, options, testsDir: string, runJoinedTest(r, cat, testsDir) else: var testsRun = 0 - for name in os.walkFiles("tests" & DirSep &.? cat.string / "t*.nim"): + + var files: seq[string] + for file in walkDirRec("tests" & DirSep &.? cat.string): + if isTestFile(file): files.add file + files.sort # give reproducible order + + for i, name in files: var test = makeTest(name, options, cat) if runJoinableTests or not isJoinableSpec(test.spec) or cat.string in specialCategories: discard "run the test" diff --git a/testament/tester.nim b/testament/tester.nim index 1f3b96059..9e5fe5830 100644 --- a/testament/tester.nim +++ b/testament/tester.nim @@ -16,6 +16,7 @@ import var useColors = true var backendLogging = true +var simulate = false const resultsFile = "testresults.html" @@ -33,6 +34,7 @@ Arguments: arguments are passed to the compiler Options: --print also print results to the console + --simulate see what tests would be run but don't run them (for debugging) --failing only show failing/ignored tests --targets:"c c++ js objc" run tests for specified targets (default: all) --nim:path use a particular nim executable (default: compiler/nim) @@ -219,7 +221,11 @@ proc `$`(x: TResults): string = proc addResult(r: var TResults, test: TTest, target: TTarget, expected, given: string, success: TResultEnum) = - let name = test.name.extractFilename & " " & $target & test.options + # test.name is easier to find than test.name.extractFilename + # A bit hacky but simple and works with tests/testament/tshouldfail.nim + var name = test.name.replace(DirSep, '/') + name.add " " & $target & test.options + let duration = epochTime() - test.startTime let durationStr = duration.formatFloat(ffDecimal, precision = 8).align(11) if backendLogging: @@ -376,6 +382,13 @@ proc testSpec(r: var TResults, test: TTest, targets: set[TTarget] = {}) = r.addResult(test, target, "", "", reDisabled) inc(r.skipped) continue + + if simulate: + var count {.global.} = 0 + count.inc + echo "testSpec count: ", count, " expected: ", expected + continue + case expected.action of actionCompile: var given = callCompiler(expected.getCmd, test.name, test.options, target, @@ -477,14 +490,30 @@ proc makeTest(test, options: string, cat: Category): TTest = result.spec = parseSpec(addFileExt(test, ".nim")) result.startTime = epochTime() +# TODO: fix these files +const disabledFilesDefault = @[ + "LockFreeHash.nim", + "sharedstrings.nim", + "tableimpl.nim", + + # Error: undeclared identifier: 'hasThreadSupport' + "ioselectors_epoll.nim", + "ioselectors_kqueue.nim", + "ioselectors_poll.nim", + + # Error: undeclared identifier: 'Timeval' + "ioselectors_select.nim", +] + when defined(windows): const # array of modules disabled from compilation test of stdlib. - disabledFiles = ["coro.nim"] + disabledFiles = disabledFilesDefault & @["coro.nim"] else: const # array of modules disabled from compilation test of stdlib. - disabledFiles = ["-"] + # TODO: why the ["-"]? (previous code should've prob used seq[string] = @[] instead) + disabledFiles = disabledFilesDefault & @["-"] include categories @@ -521,6 +550,8 @@ proc main() = useColors = false else: quit Usage + of "simulate": + simulate = true of "backendlogging": case p.val.string: of "on": @@ -547,16 +578,28 @@ proc main() = myself &= " " & quoteShell("--nim:" & compilerPrefix) - var cmds: seq[string] = @[] + var cats: seq[string] let rest = if p.cmdLineRest.string.len > 0: " " & p.cmdLineRest.string else: "" for kind, dir in walkDir(testsDir): assert testsDir.startsWith(testsDir) let cat = dir[testsDir.len .. ^1] if kind == pcDir and cat notin ["testdata", "nimcache"]: - cmds.add(myself & " pcat " & quoteShell(cat) & rest) - for cat in AdditionalCategories: + cats.add cat + cats.add AdditionalCategories + + var cmds: seq[string] + for cat in cats: cmds.add(myself & " pcat " & quoteShell(cat) & rest) - quit osproc.execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath, poParentStreams}) + + proc progressStatus(idx: int) = + echo "progress[all]: i: " & $idx & " / " & $cats.len & " cat: " & cats[idx] + + if simulate: + for i, cati in cats: + progressStatus(i) + processCategory(r, Category(cati), p.cmdLineRest.string, testsDir, runJoinableTests = false) + else: + quit osproc.execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath, poParentStreams}, beforeRunEvent = progressStatus) of "c", "cat", "category": var cat = Category(p.key) p.next diff --git a/tests/testament/tshouldfail.nim b/tests/testament/tshouldfail.nim index d35dd99ac..ebf941fab 100644 --- a/tests/testament/tshouldfail.nim +++ b/tests/testament/tshouldfail.nim @@ -2,30 +2,30 @@ discard """ cmd: "testament/tester --directory:testament --colors:off --backendLogging:off --nim:../compiler/nim category shouldfail" action: compile nimout: ''' -FAIL: tccodecheck.nim C +FAIL: tests/shouldfail/tccodecheck.nim C Failure: reCodegenFailure Expected: baz -FAIL: tcolumn.nim C +FAIL: tests/shouldfail/tcolumn.nim C Failure: reLinesDiffer -FAIL: terrormsg.nim C +FAIL: tests/shouldfail/terrormsg.nim C Failure: reMsgsDiffer -FAIL: texitcode1.nim C +FAIL: tests/shouldfail/texitcode1.nim C Failure: reExitcodesDiffer -FAIL: tfile.nim C +FAIL: tests/shouldfail/tfile.nim C Failure: reFilesDiffer -FAIL: tline.nim C +FAIL: tests/shouldfail/tline.nim C Failure: reLinesDiffer -FAIL: tmaxcodesize.nim C +FAIL: tests/shouldfail/tmaxcodesize.nim C Failure: reCodegenFailure max allowed size: 1 -FAIL: tnimout.nim C +FAIL: tests/shouldfail/tnimout.nim C Failure: reMsgsDiffer -FAIL: toutput.nim C +FAIL: tests/shouldfail/toutput.nim C Failure: reOutputsDiffer -FAIL: toutputsub.nim C +FAIL: tests/shouldfail/toutputsub.nim C Failure: reOutputsDiffer -FAIL: tsortoutput.nim C +FAIL: tests/shouldfail/tsortoutput.nim C Failure: reOutputsDiffer ''' """ -- cgit 1.4.1-2-gfad0 From 23448a96cca021d0fecd355e9d679fbb11b67102 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 21 Dec 2018 11:48:43 +0100 Subject: fixes #10058 [backport] --- compiler/vmgen.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 1f2a3e6d1..afadb4169 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1736,8 +1736,9 @@ proc genVarSection(c: PCtx; n: PNode) = #assert(a.sons[0].kind == nkSym) can happen for transformed vars if a.kind == nkVarTuple: for i in 0 .. a.len-3: - if not a[i].sym.isGlobal: setSlot(c, a[i].sym) - checkCanEval(c, a[i]) + if a[i].kind == nkSym: + if not a[i].sym.isGlobal: setSlot(c, a[i].sym) + checkCanEval(c, a[i]) c.gen(lowerTupleUnpacking(c.graph, a, c.getOwner)) elif a.sons[0].kind == nkSym: let s = a.sons[0].sym -- cgit 1.4.1-2-gfad0 From 52e36a19a902edcc533c0e0ffd0eff8c83158fef Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Fri, 21 Dec 2018 03:29:45 -0800 Subject: fix #9933 (#10067) --- compiler/main.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/main.nim b/compiler/main.nim index 4e28ed483..2b2907794 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -76,6 +76,8 @@ proc commandCompileToC(graph: ModuleGraph) = registerPass(graph, cgenPass) compileProject(graph) + if graph.config.errorCounter > 0: + return # issue #9933 cgenWriteModules(graph.backend, conf) if conf.cmd != cmdRun: let proj = changeFileExt(conf.projectFull, "") -- cgit 1.4.1-2-gfad0 From b6257f3f212036216fb4aeb521a3d120222b237c Mon Sep 17 00:00:00 2001 From: alaviss Date: Fri, 21 Dec 2018 20:12:48 +0700 Subject: os.walkDir: correctly evaluate paths when relative = true (#10057) [backport] --- lib/pure/os.nim | 9 +++++---- tests/stdlib/tos.nim | 8 ++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 2919b39f0..330d2d6b8 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1512,8 +1512,9 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: var y = $x.d_name.cstring if y != "." and y != "..": var s: Stat + let path = dir / y if not relative: - y = dir / y + y = path var k = pcFile when defined(linux) or defined(macosx) or @@ -1521,16 +1522,16 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: if x.d_type != DT_UNKNOWN: if x.d_type == DT_DIR: k = pcDir if x.d_type == DT_LNK: - if dirExists(y): k = pcLinkToDir + if dirExists(path): k = pcLinkToDir else: k = pcLinkToFile yield (k, y) continue - if lstat(y, s) < 0'i32: break + if lstat(path, s) < 0'i32: break if S_ISDIR(s.st_mode): k = pcDir elif S_ISLNK(s.st_mode): - k = getSymlinkFileKind(y) + k = getSymlinkFileKind(path) yield (k, y) iterator walkDirRec*(dir: string, diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim index 577baea87..a7cf5d5b6 100644 --- a/tests/stdlib/tos.nim +++ b/tests/stdlib/tos.nim @@ -189,6 +189,14 @@ block walkDirRec: removeDir("walkdir_test") +when not defined(windows): + block walkDirRelative: + createDir("walkdir_test") + createSymlink(".", "walkdir_test/c") + for k, p in walkDir("walkdir_test", true): + doAssert k == pcLinkToDir + removeDir("walkdir_test") + block normalizedPath: doAssert normalizedPath("") == "" block relative: -- cgit 1.4.1-2-gfad0 From 0c97d3a41c00431f22fe9d15934b3c2321c0dee1 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 21 Dec 2018 14:25:56 +0100 Subject: fixes #10066 --- lib/pure/os.nim | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 330d2d6b8..b6cef191b 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -19,7 +19,9 @@ include "system/inclrtl" import strutils, pathnorm -when defined(nimscript): +const weirdTarget = defined(nimscript) or defined(js) + +when weirdTarget: discard elif defined(windows): import winlean, times @@ -31,7 +33,7 @@ elif defined(posix): else: {.error: "OS module not ported to your operating system!".} -when defined(nimscript) and defined(nimErrorProcCanHaveBody): +when weirdTarget and defined(nimErrorProcCanHaveBody): {.pragma: noNimScript, error: "this proc is not available on the NimScript target".} else: {.pragma: noNimScript.} @@ -662,7 +664,7 @@ when defined(windows) or defined(posix) or defined(nintendoswitch): if i > 0: result.add " " result.add quoteShell(args[i]) -when not defined(nimscript): +when not weirdTarget: proc c_rename(oldname, newname: cstring): cint {. importc: "rename", header: "".} proc c_system(cmd: cstring): cint {. @@ -673,7 +675,7 @@ when not defined(nimscript): importc: "free", header: "".} -when defined(windows) and not defined(nimscript): +when defined(windows) and not weirdTarget: when useWinUnicode: template wrapUnary(varname, winApiProc, arg: untyped) = var varname = winApiProc(newWideCString(arg)) @@ -755,7 +757,7 @@ proc dirExists*(dir: string): bool {.inline, noNimScript.} = ## Synonym for existsDir existsDir(dir) -when not defined(windows) and not defined(nimscript): +when not defined(windows) and not weirdTarget: proc checkSymlink(path: string): bool = var rawInfo: Stat if lstat(path, rawInfo) < 0'i32: result = false @@ -816,7 +818,7 @@ proc findExe*(exe: string, followSymlinks: bool = true; return x result = "" -when defined(nimscript): +when weirdTarget: const times = "fake const" template Time(x: untyped): untyped = string @@ -931,7 +933,7 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [], noNimScript.} = else: if chdir(newDir) != 0'i32: raiseOSError(osLastError()) -when not defined(nimscript): +when not weirdTarget: proc absolutePath*(path: string, root = getCurrentDir()): string {.noNimScript.} = ## Returns the absolute path of `path`, rooted at `root` (which must be absolute) ## if `path` is absolute, return it, ignoring `root` @@ -1027,7 +1029,7 @@ proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [], noN ## Returns a normalized path for the current OS. See `<#normalizePath>`_ result = pathnorm.normalizePath(path) -when defined(Windows) and not defined(nimscript): +when defined(Windows) and not weirdTarget: proc openHandle(path: string, followSymlink=true, writeAccess=false): Handle = var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL if not followSymlink: @@ -1250,7 +1252,7 @@ when not declared(ENOENT) and not defined(Windows): else: var ENOENT {.importc, header: "".}: cint -when defined(Windows) and not defined(nimscript): +when defined(Windows) and not weirdTarget: when useWinUnicode: template deleteFile(file: untyped): untyped = deleteFileW(file) template setFileAttributes(file, attrs: untyped): untyped = @@ -1343,15 +1345,15 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", result = c_system(command) # Templates for filtering directories and files -when defined(windows) and not defined(nimscript): +when defined(windows) and not weirdTarget: template isDir(f: WIN32_FIND_DATA): bool = (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32 template isFile(f: WIN32_FIND_DATA): bool = not isDir(f) else: - template isDir(f: string): bool = + template isDir(f: string): bool {.dirty.} = dirExists(f) - template isFile(f: string): bool = + template isFile(f: string): bool {.dirty.} = fileExists(f) template defaultWalkFilter(item): bool = @@ -1436,7 +1438,7 @@ proc getCurrentCompilerExe*(): string {.compileTime.} = discard ## inside a nimble program (likewise with other binaries built from ## compiler API). -when defined(posix) and not defined(nimscript): +when defined(posix) and not weirdTarget: proc getSymlinkFileKind(path: string): PathComponent = # Helper function. var s: Stat @@ -1477,7 +1479,7 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: for k, v in items(staticWalkDir(dir, relative)): yield (k, v) else: - when defined(nimscript): + when weirdTarget: for k, v in items(staticWalkDir(dir, relative)): yield (k, v) elif defined(windows): @@ -1972,7 +1974,7 @@ when defined(nimdoc): ## else: ## # Do something else! -elif defined(nintendoswitch) or defined(nimscript): +elif defined(nintendoswitch) or weirdTarget: proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} = raise newException(OSError, "paramStr is not implemented on Nintendo Switch") @@ -2049,7 +2051,7 @@ when declared(paramCount) or defined(nimdoc): for i in 1..paramCount(): result.add(paramStr(i)) -when not defined(nimscript) and (defined(freebsd) or defined(dragonfly)): +when not weirdTarget and (defined(freebsd) or defined(dragonfly)): proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize, newp: pointer, newplen: csize): cint {.importc: "sysctl",header: """#include @@ -2082,7 +2084,7 @@ when not defined(nimscript) and (defined(freebsd) or defined(dragonfly)): result.setLen(pathLength) break -when not defined(nimscript) and (defined(linux) or defined(solaris) or defined(bsd) or defined(aix)): +when not weirdTarget and (defined(linux) or defined(solaris) or defined(bsd) or defined(aix)): proc getApplAux(procPath: string): string = result = newString(256) var len = readlink(procPath, result, 256) @@ -2091,7 +2093,7 @@ when not defined(nimscript) and (defined(linux) or defined(solaris) or defined(b len = readlink(procPath, result, len) setLen(result, len) -when not (defined(windows) or defined(macosx) or defined(nimscript)): +when not (defined(windows) or defined(macosx) or weirdTarget): proc getApplHeuristic(): string = when declared(paramStr): result = string(paramStr(0)) @@ -2227,7 +2229,7 @@ proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1", close(f) else: raiseOSError(osLastError()) -when defined(Windows) or defined(nimscript): +when defined(Windows) or weirdTarget: type DeviceId* = int32 FileId* = int64 @@ -2307,6 +2309,12 @@ template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = assert(path != "") # symlinks can't occur for file handles formalInfo.kind = getSymlinkFileKind(path) +when defined(js): + when not declared(FileHandle): + type FileHandle = distinct int32 + when not declared(File): + type File = object + proc getFileInfo*(handle: FileHandle): FileInfo {.noNimScript.} = ## Retrieves file information for the file object represented by the given ## handle. -- cgit 1.4.1-2-gfad0 From da809925773c112f5b26add9e86e9f4837342372 Mon Sep 17 00:00:00 2001 From: hlaaf Date: Fri, 21 Dec 2018 20:10:07 +0300 Subject: Add system.$(HSlice) and fix #7898 (#8670) * Add system.$(HSlice) and fix #7898 * Surround system.$(HSlice) with spaces --- lib/system.nim | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/system.nim b/lib/system.nim index 32a24323e..e998ffee0 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2656,7 +2656,7 @@ proc `$`*[T: tuple|object](x: T): string = ## of `x`. Example: ## ## .. code-block:: nim - ## $(23, 45) == "(23, 45)" + ## $(23, 45) == "(Field0: 23, Field1: 45)" ## $() == "()" result = "(" var firstElement = true @@ -2711,6 +2711,16 @@ proc `$`*[T](x: seq[T]): string = ## $(@[23, 45]) == "@[23, 45]" collectionToString(x, "@[", ", ", "]") +proc `$`*[T, U](x: HSlice[T, U]): string = + ## generic ``$`` operator for slices that is lifted from the components + ## of `x`. Example: + ## + ## .. code-block:: nim + ## $(1 .. 5) == "1 .. 5" + result = $x.a + result.add(" .. ") + result.add($x.b) + # ----------------- GC interface --------------------------------------------- when not defined(nimscript) and hasAlloc: -- cgit 1.4.1-2-gfad0 From 4a6d699bc5651e834622ea8653d6a1a03ac75bd2 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 21 Dec 2018 17:16:14 +0100 Subject: C++: make async tests green on Windows --- compiler/sigmatch.nim | 10 +++++----- lib/windows/winlean.nim | 2 +- tests/async/tasynctry.nim | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index d66e8d121..0915f303b 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -2231,14 +2231,14 @@ proc matchesAux(c: PContext, n, nOrig: PNode, else: m.state = csNoMatch return - + if formal.typ.kind == tyVar: - let arg_converter = if arg.kind == nkHiddenDeref: arg[0] else: arg - if arg_converter.kind == nkHiddenCallConv: - if arg_converter.typ.kind != tyVar: + let argConverter = if arg.kind == nkHiddenDeref: arg[0] else: arg + if argConverter.kind == nkHiddenCallConv: + if argConverter.typ.kind != tyVar: m.state = csNoMatch m.mutabilityProblem = uint8(f-1) - return + return elif not n.isLValue: m.state = csNoMatch m.mutabilityProblem = uint8(f-1) diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 3e37b824c..1be982767 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -516,7 +516,7 @@ type fd_count*: cint # unsigned fd_array*: array[0..FD_SETSIZE-1, SocketHandle] - Timeval* = object + Timeval* {.importc: "timeval", header: "".} = object tv_sec*, tv_usec*: int32 AddrInfo* = object diff --git a/tests/async/tasynctry.nim b/tests/async/tasynctry.nim index b13c57951..a7cb5223d 100644 --- a/tests/async/tasynctry.nim +++ b/tests/async/tasynctry.nim @@ -6,6 +6,7 @@ Multiple idents in except Multiple except branches Multiple except branches 2 ''' +targets: "c" """ import asyncdispatch, strutils -- cgit 1.4.1-2-gfad0 From 237085db5d7ff29a2d0e18937f08f1769c38433a Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 21 Dec 2018 22:03:13 +0100 Subject: C++ tests: make DLL test green --- compiler/ccgtypes.nim | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index ed4aa8001..235bd16d8 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -542,7 +542,13 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope, # proper request to generate popCurrentExceptionEx not possible for 2 reasons: # generated function will be below declared Exception type and circular dependency # between Exception and popCurrentExceptionEx function - result = genProcHeader(m, magicsys.getCompilerProc(m.g.graph, "popCurrentExceptionEx")) & ";" & result + + let popExSym = magicsys.getCompilerProc(m.g.graph, "popCurrentExceptionEx") + if lfDynamicLib in popExSym.loc.flags and sfImportc in popExSym.flags: + # echo popExSym.flags, " ma flags ", popExSym.loc.flags + result = "extern " & getTypeDescAux(m, popExSym.typ, check) & " " & mangleName(m, popExSym) & ";\L" & result + else: + result = genProcHeader(m, popExSym) & ";\L" & result hasField = true else: appcg(m, result, " {$n $1 Sup;$n", -- cgit 1.4.1-2-gfad0 From 39a8ab469a8596a413d75d85e3ed7587783b353d Mon Sep 17 00:00:00 2001 From: Yuriy Glukhov Date: Fri, 21 Dec 2018 23:45:20 +0200 Subject: Fixed insert for nil seq in js (#10068) --- lib/system.nim | 2 +- tests/js/test1.nim | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/system.nim b/lib/system.nim index e998ffee0..06aa25311 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1692,7 +1692,7 @@ proc insert*[T](x: var seq[T], item: T, i = 0.Natural) {.noSideEffect.} = else: when defined(js): var it : T - {.emit: "`x`.splice(`i`, 0, `it`);".} + {.emit: "`x` = `x` || []; `x`.splice(`i`, 0, `it`);".} else: defaultImpl() x[i] = item diff --git a/tests/js/test1.nim b/tests/js/test1.nim index 7f1d346f0..73e7a37ed 100644 --- a/tests/js/test1.nim +++ b/tests/js/test1.nim @@ -20,3 +20,35 @@ proc onButtonClick(inputElement: string) {.exportc.} = onButtonClick(inputElement) +block: + var s: string + s.add("hi") + doAssert(s == "hi") + +block: + var s: string + s.insert("hi", 0) + doAssert(s == "hi") + +block: + var s: string + s.setLen(2) + s[0] = 'h' + s[1] = 'i' + doAssert(s == "hi") + +block: + var s: seq[int] + s.setLen(2) + doAssert(s == @[0, 0]) + +block: + var s: seq[int] + s.insert(2, 0) + doAssert(s == @[2]) + +block: + var s: seq[int] + s.add(2) + doAssert(s == @[2]) + -- cgit 1.4.1-2-gfad0 From 8aa5d126fe5bd2b0b01997cbd12799940b0ae33c Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Fri, 21 Dec 2018 13:52:28 -0800 Subject: clarify doc for <= on tuple uses lexicographic order (#10069) --- lib/system.nim | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/system.nim b/lib/system.nim index 06aa25311..761794da6 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2624,8 +2624,8 @@ proc `==`*[T: tuple|object](x, y: T): bool = return true proc `<=`*[T: tuple](x, y: T): bool = - ## generic ``<=`` operator for tuples that is lifted from the components - ## of `x` and `y`. This implementation uses `cmp`. + ## generic lexicographic ``<=`` operator for tuples that is lifted from the + ## components of `x` and `y`. This implementation uses `cmp`. for a, b in fields(x, y): var c = cmp(a, b) if c < 0: return true @@ -2633,8 +2633,8 @@ proc `<=`*[T: tuple](x, y: T): bool = return true proc `<`*[T: tuple](x, y: T): bool = - ## generic ``<`` operator for tuples that is lifted from the components - ## of `x` and `y`. This implementation uses `cmp`. + ## generic lexicographic ``<`` operator for tuples that is lifted from the + ## components of `x` and `y`. This implementation uses `cmp`. for a, b in fields(x, y): var c = cmp(a, b) if c < 0: return true -- cgit 1.4.1-2-gfad0 From 8869aa74dba5559805909e3fd8d4db27d6e0cc37 Mon Sep 17 00:00:00 2001 From: Taylor Hoff Date: Fri, 21 Dec 2018 16:52:57 -0500 Subject: Stop false positives in distros.detectOs (#10062) * Stop false positives in distros.nim * Fix bad string comparison on line 176 of distros.nim --- lib/pure/distros.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pure/distros.nim b/lib/pure/distros.nim index 4bf1c3da3..0f1ffb1ab 100644 --- a/lib/pure/distros.nim +++ b/lib/pure/distros.nim @@ -141,8 +141,8 @@ template unameRelease(cmd, cache): untyped = cache = (when defined(nimscript): gorge(cmd) else: execProcess(cmd)) cache -template uname(): untyped = unameRelease("uname -a", unameRes) -template release(): untyped = unameRelease("lsb_release -a", releaseRes) +template uname(): untyped = unameRelease("uname -o", unameRes) +template release(): untyped = unameRelease("lsb_release -d", releaseRes) template hostnamectl(): untyped = unameRelease("hostnamectl", hostnamectlRes) proc detectOsImpl(d: Distribution): bool = @@ -173,7 +173,7 @@ proc detectOsImpl(d: Distribution): bool = result = defined(haiku) else: let dd = toLowerAscii($d) - result = dd in toLowerAscii(uname()) or dd in toLowerAscii(release()) or dd in toLowerAscii(hostnamectl()) + result = dd in toLowerAscii(uname()) or dd in toLowerAscii(release()) or ("operating system: " & dd) in toLowerAscii(hostnamectl()) template detectOs*(d: untyped): bool = ## Distro/OS detection. For convenience the -- cgit 1.4.1-2-gfad0 From bec493925f4573b34c9f188e0041a3d5c2a7467c Mon Sep 17 00:00:00 2001 From: Alexis Hope Date: Sat, 22 Dec 2018 08:55:58 +1100 Subject: IntSet documentation notes (#10051) --- lib/pure/collections/intsets.nim | 47 ++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/lib/pure/collections/intsets.nim b/lib/pure/collections/intsets.nim index def96b8f7..8034e4c0a 100644 --- a/lib/pure/collections/intsets.nim +++ b/lib/pure/collections/intsets.nim @@ -9,9 +9,12 @@ ## The ``intsets`` module implements an efficient int set implemented as a ## `sparse bit set`:idx:. -## **Note**: Since Nim currently does not allow the assignment operator to -## be overloaded, ``=`` for int sets performs some rather meaningless shallow -## copy; use ``assign`` to get a deep copy. + +## **Note**: Currently the assignment operator ``=`` for ``intsets`` +## performs some rather meaningless shallow copy. Since Nim currently does +## not allow the assignment operator to be overloaded, use ``assign`` to +## get a deep copy. + import hashes, math @@ -94,7 +97,7 @@ proc intSetPut(t: var IntSet, key: int): PTrunk = t.data[h] = result proc contains*(s: IntSet, key: int): bool = - ## returns true iff `key` is in `s`. + ## Returns true iff `key` is in `s`. if s.elems <= s.a.len: for i in 0..` _. + ## Alias for `len() <#len>` _. result = s.len() proc `<=`*(s1, s2: IntSet): bool = @@ -350,7 +359,7 @@ proc `$`*(s: IntSet): string = dollarImpl() proc empty*(s: IntSet): bool {.inline, deprecated.} = - ## returns true if `s` is empty. This is safe to call even before + ## Returns true if `s` is empty. This is safe to call even before ## the set has been initialized with `initIntSet`. Note this never ## worked reliably and so is deprecated. result = s.counter == 0 -- cgit 1.4.1-2-gfad0 From ca672ec62e8c12bb1ea03da2fa6f0113924d0dbd Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Sat, 22 Dec 2018 09:47:22 +0100 Subject: appveyor: make the build green again --- lib/windows/winlean.nim | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 1be982767..6c480d03a 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -516,9 +516,6 @@ type fd_count*: cint # unsigned fd_array*: array[0..FD_SETSIZE-1, SocketHandle] - Timeval* {.importc: "timeval", header: "".} = object - tv_sec*, tv_usec*: int32 - AddrInfo* = object ai_flags*: cint ## Input flags. ai_family*: cint ## Address family of socket. @@ -531,6 +528,14 @@ type SockLen* = cuint +when defined(cpp): + type + Timeval* {.importc: "timeval", header: "".} = object + tv_sec*, tv_usec*: int32 +else: + type + Timeval* = object + tv_sec*, tv_usec*: int32 var SOMAXCONN* {.importc, header: "winsock2.h".}: cint -- cgit 1.4.1-2-gfad0 From 253936385143cb9d58f565dee0f1b5768accc81e Mon Sep 17 00:00:00 2001 From: Oscar Nihlgård Date: Sat, 22 Dec 2018 10:41:54 +0100 Subject: Don't use parseutils.parseInt in the times module (#10028) --- lib/pure/times.nim | 106 ++++++++++++++++++++++++++++++------------------ tests/stdlib/ttimes.nim | 11 +++++ 2 files changed, 78 insertions(+), 39 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 010551b5a..7afc3dade 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -122,12 +122,8 @@ only for years in the range 1..9999). ]## - -{.push debugger:off.} # the user does not want to trace a part - # of the standard library! - import - strutils, parseutils, algorithm, math, options, strformat + strutils, algorithm, math, options, strformat include "system/inclrtl" @@ -304,7 +300,6 @@ const secondsInMin = 60 secondsInHour = 60*60 secondsInDay = 60*60*24 - minutesInHour = 60 rateDiff = 10000000'i64 # 100 nsecs # The number of hectonanoseconds between 1601/01/01 (windows epoch) # and 1970/01/01 (unix epoch). @@ -1602,6 +1597,12 @@ type ## be encoded as ``@[Lit.byte, 3.byte, 'f'.byte, 'o'.byte, 'o'.byte]``. formatStr: string + TimeParseError* = object of ValueError ## \ + ## Raised when parsing input using a ``TimeFormat`` fails. + + TimeFormatParseError* = object of ValueError ## \ + ## Raised when parsing a ``TimeFormat`` string fails. + const FormatLiterals = { ' ', '-', '/', ':', '(', ')', '[', ']', ',' } proc `$`*(f: TimeFormat): string = @@ -1612,9 +1613,34 @@ proc `$`*(f: TimeFormat): string = f.formatStr proc raiseParseException(f: TimeFormat, input: string, msg: string) = - raise newException(ValueError, + raise newException(TimeParseError, &"Failed to parse '{input}' with format '{f}'. {msg}") +proc parseInt(s: string, b: var int, start = 0, maxLen = int.high, + allowSign = false): int = + var sign = -1 + var i = start + let stop = start + min(s.high - start + 1, maxLen) - 1 + if allowSign and i <= stop: + if s[i] == '+': + inc(i) + elif s[i] == '-': + inc(i) + sign = 1 + if i <= stop and s[i] in {'0'..'9'}: + b = 0 + while i <= stop and s[i] in {'0'..'9'}: + let c = ord(s[i]) - ord('0') + if b >= (low(int) + c) div 10: + b = b * 10 - c + else: + return 0 + inc(i) + if sign == -1 and b == low(int): + return 0 + b = b * sign + result = i - start + iterator tokens(f: string): tuple[kind: FormatTokenKind, token: string] = var i = 0 var currToken = "" @@ -1639,7 +1665,7 @@ iterator tokens(f: string): tuple[kind: FormatTokenKind, token: string] = i.inc if i > f.high: - raise newException(ValueError, + raise newException(TimeFormatParseError, &"Unclosed ' in time format string. " & "For a literal ', use ''.") i.inc @@ -1696,7 +1722,8 @@ proc stringToPattern(str: string): FormatPattern = of "zzz": result = zzz of "zzzz": result = zzzz of "g": result = g - else: raise newException(ValueError, &"'{str}' is not a valid pattern") + else: raise newException(TimeFormatParseError, + &"'{str}' is not a valid pattern") proc initTimeFormat*(format: string): TimeFormat = ## Construct a new time format for parsing & formatting time types. @@ -1715,7 +1742,7 @@ proc initTimeFormat*(format: string): TimeFormat = else: result.patterns.add(FormatPattern.Lit.byte) if token.len > 255: - raise newException(ValueError, + raise newException(TimeFormatParseError, "Format literal is to long:" & token) result.patterns.add(token.len.byte) for c in token: @@ -1833,15 +1860,10 @@ proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string) = proc parsePattern(input: string, pattern: FormatPattern, i: var int, parsed: var ParsedTime): bool = - template takeInt(allowedWidth: Slice[int]): int = + template takeInt(allowedWidth: Slice[int], allowSign = false): int = var sv: int - let max = i + allowedWidth.b - 1 - var pd = - if max > input.high: - parseInt(input, sv, i) - else: - parseInt(input[i..max], sv) - if pd notin allowedWidth: + var pd = parseInt(input, sv, i, allowedWidth.b, allowSign) + if pd < allowedWidth.a: return false i.inc pd sv @@ -1853,11 +1875,13 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int, case pattern of d: - parsed.monthday = some(takeInt(1..2)) - result = parsed.monthday.get() in MonthdayRange + let monthday = takeInt(1..2) + parsed.monthday = some(monthday) + result = monthday in MonthdayRange of dd: - parsed.monthday = some(takeInt(2..2)) - result = parsed.monthday.get() in MonthdayRange + let monthday = takeInt(2..2) + parsed.monthday = some(monthday) + result = monthday in MonthdayRange of ddd: result = input.substr(i, i+2).toLowerAscii() in [ "sun", "mon", "tue", "wed", "thu", "fri", "sat"] @@ -1993,7 +2017,7 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int, of yyyy: let year = if input[i] in { '+', '-' }: - takeInt(4..high(int)) + takeInt(4..high(int), allowSign = true) else: takeInt(4..4) result = year > 0 @@ -2005,12 +2029,12 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int, of uuuu: let year = if input[i] in { '+', '-' }: - takeInt(4..high(int)) + takeInt(4..high(int), allowSign = true) else: takeInt(4..4) parsed.year = some(year) of UUUU: - parsed.year = some(takeInt(1..high(int))) + parsed.year = some(takeInt(1..high(int), allowSign = true)) of z, zz, zzz, zzzz: case input[i] of '+', '-': @@ -2055,9 +2079,8 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int, else: result = false of y, yyy, yyyyy: - raise newException(ValueError, - &"The pattern '{pattern}' is only valid for formatting") - of Lit: assert false # Can't happen + raiseAssert "Pattern is invalid for parsing: " & $pattern + of Lit: doAssert false, "Can't happen" proc toDateTime(p: ParsedTime, zone: Timezone, f: TimeFormat, input: string): DateTime = @@ -2147,7 +2170,8 @@ proc format*(dt: DateTime, f: TimeFormat): string {.raises: [].} = formatPattern(dt, f.patterns[idx].FormatPattern, result = result) idx.inc -proc format*(dt: DateTime, f: string): string = +proc format*(dt: DateTime, f: string): string + {.raises: [TimeFormatParseError].} = ## Shorthand for constructing a ``TimeFormat`` and using it to format ``dt``. ## ## See `Parsing and formatting dates`_ for documentation of the @@ -2163,7 +2187,8 @@ proc format*(dt: DateTime, f: static[string]): string {.raises: [].} = const f2 = initTimeFormat(f) result = dt.format(f2) -proc format*(time: Time, f: string, zone: Timezone = local()): string {.tags: [].} = +proc format*(time: Time, f: string, zone: Timezone = local()): string + {.raises: [TimeFormatParseError].} = ## Shorthand for constructing a ``TimeFormat`` and using it to format ## ``time``. Will use the timezone specified by ``zone``. ## @@ -2175,13 +2200,14 @@ proc format*(time: Time, f: string, zone: Timezone = local()): string {.tags: [] doAssert format(tm, "yyyy-MM-dd'T'HH:mm:ss", utc()) == "1970-01-01T00:00:00" time.inZone(zone).format(f) -proc format*(time: Time, f: static[string], - zone: Timezone = local()): string {.tags: [].} = +proc format*(time: Time, f: static[string], zone: Timezone = local()): string + {.raises: [].} = ## Overload that validates ``f`` at compile time. const f2 = initTimeFormat(f) result = time.inZone(zone).format(f2) -proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime = +proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime + {.raises: [TimeParseError, Defect].} = ## Parses ``input`` as a ``DateTime`` using the format specified by ``f``. ## If no UTC offset was parsed, then ``input`` is assumed to be specified in ## the ``zone`` timezone. If a UTC offset was parsed, the result will be @@ -2221,7 +2247,8 @@ proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime = result = toDateTime(parsed, zone, f, input) -proc parse*(input, f: string, tz: Timezone = local()): DateTime = +proc parse*(input, f: string, tz: Timezone = local()): DateTime + {.raises: [TimeParseError, TimeFormatParseError, Defect].} = ## Shorthand for constructing a ``TimeFormat`` and using it to parse ## ``input`` as a ``DateTime``. ## @@ -2233,12 +2260,14 @@ proc parse*(input, f: string, tz: Timezone = local()): DateTime = let dtFormat = initTimeFormat(f) result = input.parse(dtFormat, tz) -proc parse*(input: string, f: static[string], zone: Timezone = local()): DateTime = +proc parse*(input: string, f: static[string], zone: Timezone = local()): DateTime + {.raises: [TimeParseError, Defect].} = ## Overload that validates ``f`` at compile time. const f2 = initTimeFormat(f) result = input.parse(f2, zone) -proc parseTime*(input, f: string, zone: Timezone): Time = +proc parseTime*(input, f: string, zone: Timezone): Time + {.raises: [TimeParseError, TimeFormatParseError, Defect].} = ## Shorthand for constructing a ``TimeFormat`` and using it to parse ## ``input`` as a ``DateTime``, then converting it a ``Time``. ## @@ -2249,7 +2278,8 @@ proc parseTime*(input, f: string, zone: Timezone): Time = doAssert parseTime(tStr, "yyyy-MM-dd'T'HH:mm:sszzz", utc()) == fromUnix(0) parse(input, f, zone).toTime() -proc parseTime*(input: string, f: static[string], zone: Timezone): Time = +proc parseTime*(input: string, f: static[string], zone: Timezone): Time + {.raises: [TimeParseError, Defect].} = ## Overload that validates ``format`` at compile time. const f2 = initTimeFormat(f) result = input.parse(f2, zone).toTime() @@ -2275,8 +2305,6 @@ proc `$`*(time: Time): string {.tags: [], raises: [], benign.} = doAssert $tm == "1970-01-01T00:00:00" & format(dt, "zzz") $time.local -{.pop.} - proc countLeapYears*(yearSpan: int): int = ## Returns the number of leap years spanned by a given number of years. ## diff --git a/tests/stdlib/ttimes.nim b/tests/stdlib/ttimes.nim index ed87b15ac..32f39953d 100644 --- a/tests/stdlib/ttimes.nim +++ b/tests/stdlib/ttimes.nim @@ -245,6 +245,17 @@ suite "ttimes": parseTestExcp("12345", "uuuu") parseTestExcp("-1 BC", "UUUU g") + test "incorrect inputs: invalid sign": + parseTestExcp("+1", "YYYY") + parseTestExcp("+1", "dd") + parseTestExcp("+1", "MM") + parseTestExcp("+1", "hh") + parseTestExcp("+1", "mm") + parseTestExcp("+1", "ss") + + test "_ as a seperator": + discard parse("2000_01_01", "YYYY'_'MM'_'dd") + test "dynamic timezone": let tz = staticTz(seconds = -9000) let dt = initDateTime(1, mJan, 2000, 12, 00, 00, tz) -- cgit 1.4.1-2-gfad0 From bdb67201b2713a4045167fd8feb407df98a43997 Mon Sep 17 00:00:00 2001 From: Araq Date: Sat, 22 Dec 2018 18:37:14 +0100 Subject: fixes #10033 [backport] --- compiler/ccgtypes.nim | 4 ++-- tests/objvariant/tyaoption.nim | 25 ++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 235bd16d8..243aa87de 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -970,9 +970,9 @@ proc genTypeInfoAux(m: BModule, typ, origType: PType, name: Rope; proc discriminatorTableName(m: BModule, objtype: PType, d: PSym): Rope = # bugfix: we need to search the type that contains the discriminator: - var objtype = objtype + var objtype = objtype.skipTypes(abstractPtrs) while lookupInRecord(objtype.n, d.name) == nil: - objtype = objtype.sons[0] + objtype = objtype.sons[0].skipTypes(abstractPtrs) if objtype.sym == nil: internalError(m.config, d.info, "anonymous obj with discriminator") result = "NimDT_$1_$2" % [rope($hashType(objtype)), rope(d.name.s.mangle)] diff --git a/tests/objvariant/tyaoption.nim b/tests/objvariant/tyaoption.nim index 7a29b8008..80bfa4bae 100644 --- a/tests/objvariant/tyaoption.nim +++ b/tests/objvariant/tyaoption.nim @@ -1,7 +1,8 @@ discard """ output: '''some(str), some(5), none some(5!) -some(10)''' +some(10) +34''' """ import strutils @@ -45,3 +46,25 @@ let a3 = intOrString(some(5)) #echo a1 echo a2 echo a3 + + +# bug #10033 + +type + Token = enum + Int, + Float + + Base = ref object of RootObj + case token: Token + of Int: + bInt: int + of Float: + bFloat: float + + Child = ref object of Base + +let c = new Child +c.token = Int +c.bInt = 34 +echo c.bInt -- cgit 1.4.1-2-gfad0 From d407af565f164a2182e9d72fa69d1263cd7b3f9f Mon Sep 17 00:00:00 2001 From: Araq Date: Sat, 22 Dec 2018 23:13:05 +0100 Subject: fixes #10024 --- compiler/passes.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/passes.nim b/compiler/passes.nim index 9302bb2e4..50fc13e1e 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -155,7 +155,7 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool { while true: openParsers(p, fileIdx, s, graph.cache, graph.config) - if module.owner == nil or module.owner.name.s != "stdlib": # graph.config.mainPackageId == module.owner.id: + if module.owner == nil or module.owner.name.s != "stdlib" or module.name.s == "distros": # XXX what about caching? no processing then? what if I change the # modules to include between compilation runs? we'd need to track that # in ROD files. I think we should enable this feature only -- cgit 1.4.1-2-gfad0 From e1d5356ae9fe0e289cc6fe21fec486c54f89400a Mon Sep 17 00:00:00 2001 From: c-blake Date: Sun, 23 Dec 2018 07:23:20 -0500 Subject: Add ability to sample elements from openArray according to a weight array (#10072) * Add the ability to sample elements from an openArray according to a parallel array of weights/unnormalized probabilities (any sort of histogram, basically). Also add a non-thread safe version for convenience. * Address Araq comments on https://github.com/nim-lang/Nim/pull/10072 * import at top of file and space after '#'. * Put in a check for non-zero total weight. * Clarify constraint on `w`. * Rename `rand(openArray[T])` to `sample(openArray[T])` to `sample`, deprecating old name and name new (openArray[T], openArray[U]) variants `sample`. * Rename caller-provided state version of rand(openArray[T]) and also clean up doc comments. * Add test for new non-uniform array sampler. 3 sd bound makes it 99% likely that it will still pass in the future if the random number generator changes. We cannot both have a tight bound to check distribution *and* loose check to ensure resilience to RNG changes. (We cannot *guarantee* resilience, anyway. There's always a small chance any test hits a legitimate random fluctuation.) --- lib/pure/random.nim | 37 +++++++++++++++++++++++++++++++++++-- tests/stdlib/tmath.nim | 28 +++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/lib/pure/random.nim b/lib/pure/random.nim index c458d51eb..d6501c87e 100644 --- a/lib/pure/random.nim +++ b/lib/pure/random.nim @@ -14,6 +14,8 @@ ## ## **Do not use this module for cryptographic purposes!** +import algorithm #For upperBound + include "system/inclrtl" {.push debugger:off.} @@ -155,14 +157,45 @@ proc rand*[T](x: HSlice[T, T]): T = ## For a slice `a .. b` returns a value in the range `a .. b`. result = rand(state, x) -proc rand*[T](r: var Rand; a: openArray[T]): T = +proc rand*[T](r: var Rand; a: openArray[T]): T {.deprecated.} = ## returns a random element from the openarray `a`. + ## **Deprecated since v0.20.0:** use ``sample`` instead. result = a[rand(r, a.low..a.high)] -proc rand*[T](a: openArray[T]): T = +proc rand*[T](a: openArray[T]): T {.deprecated.} = ## returns a random element from the openarray `a`. + ## **Deprecated since v0.20.0:** use ``sample`` instead. result = a[rand(a.low..a.high)] +proc sample*[T](r: var Rand; a: openArray[T]): T = + ## returns a random element from openArray ``a`` using state in ``r``. + result = a[r.rand(a.low..a.high)] + +proc sample*[T](a: openArray[T]): T = + ## returns a random element from openArray ``a`` using non-thread-safe state. + result = a[rand(a.low..a.high)] + +proc sample*[T, U](r: var Rand; a: openArray[T], w: openArray[U], n=1): seq[T] = + ## Return a sample (with replacement) of size ``n`` from elements of ``a`` + ## according to convertible-to-``float``, not necessarily normalized, and + ## non-negative weights ``w``. Uses state in ``r``. Must have sum ``w > 0.0``. + assert(w.len == a.len) + var cdf = newSeq[float](a.len) # The *unnormalized* CDF + var tot = 0.0 # Unnormalized is fine if we sample up to tot + for i, w in w: + assert(w >= 0) + tot += float(w) + cdf[i] = tot + assert(tot > 0.0) # Need at least one non-zero weight + for i in 0 ..< n: + result.add(a[cdf.upperBound(r.rand(tot))]) + +proc sample*[T, U](a: openArray[T], w: openArray[U], n=1): seq[T] = + ## Return a sample (with replacement) of size ``n`` from elements of ``a`` + ## according to convertible-to-``float``, not necessarily normalized, and + ## non-negative weights ``w``. Uses default non-thread-safe state. + state.sample(a, w, n) + proc initRand*(seed: int64): Rand = ## Creates a new ``Rand`` state from ``seed``. diff --git a/tests/stdlib/tmath.nim b/tests/stdlib/tmath.nim index 581308a7e..7c1851e7a 100644 --- a/tests/stdlib/tmath.nim +++ b/tests/stdlib/tmath.nim @@ -4,6 +4,8 @@ discard """ [Suite] random float +[Suite] random sample + [Suite] ^ ''' @@ -11,7 +13,7 @@ discard """ import math, random, os import unittest -import sets +import sets, tables suite "random int": test "there might be some randomness": @@ -72,6 +74,30 @@ suite "random float": var rand2:float = random(1000000.0) check rand1 != rand2 +suite "random sample": + test "non-uniform array sample": + let values = [ 10, 20, 30, 40, 50 ] # values + let weight = [ 4, 3, 2, 1, 0 ] # weights aka unnormalized probabilities + let weightSum = 10.0 # sum of weights + var histo = initCountTable[int]() + for v in sample(values, weight, 5000): + histo.inc(v) + check histo.len == 4 # number of non-zero in `weight` + # Any one bin is a binomial random var for n samples, each with prob p of + # adding a count to k; E[k]=p*n, Var k=p*(1-p)*n, approximately Normal for + # big n. So, P(abs(k - p*n)/sqrt(p*(1-p)*n))>3.0) =~ 0.0027, while + # P(wholeTestFails) =~ 1 - P(binPasses)^4 =~ 1 - (1-0.0027)^4 =~ 0.01. + for i, w in weight: + if w == 0: + check values[i] notin histo + continue + let p = float(w) / float(weightSum) + let n = 5000.0 + let expected = p * n + let stdDev = sqrt(n * p * (1.0 - p)) + check abs(float(histo[values[i]]) - expected) <= 3.0 * stdDev + + suite "^": test "compiles for valid types": check: compiles(5 ^ 2) -- cgit 1.4.1-2-gfad0 From 5f2b66751ac0a3115e6b4760f067332e80264195 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Sun, 23 Dec 2018 14:00:37 -0800 Subject: fix #10049 (#10083) --- testament/categories.nim | 2 +- testament/specs.nim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testament/categories.nim b/testament/categories.nim index ecaead9ef..55b9e5ed2 100644 --- a/testament/categories.nim +++ b/testament/categories.nim @@ -599,7 +599,7 @@ proc runJoinedTest(r: var TResults, cat: Category, testsDir: string) = writeFile("megatest.nim", megatest) const args = ["c", "-d:testing", "--listCmd", "megatest.nim"] - var (buf, exitCode) = execCmdEx2(command = "nim", args = args, options = {poStdErrToStdOut, poUsePath}, input = "") + var (buf, exitCode) = execCmdEx2(command = compilerPrefix, args = args, options = {poStdErrToStdOut, poUsePath}, input = "") if exitCode != 0: echo buf quit("megatest compilation failed") diff --git a/testament/specs.nim b/testament/specs.nim index df12db543..22ad7d2f8 100644 --- a/testament/specs.nim +++ b/testament/specs.nim @@ -9,7 +9,7 @@ import parseutils, strutils, os, osproc, streams, parsecfg -var compilerPrefix* = "compiler" / "nim" +var compilerPrefix* = "compiler" / "nim" ## built via ./koch tests let isTravis* = existsEnv("TRAVIS") let isAppVeyor* = existsEnv("APPVEYOR") -- cgit 1.4.1-2-gfad0 From 58d293d6a98b56ed4b309491f09492618c3fa9ec Mon Sep 17 00:00:00 2001 From: alaviss Date: Wed, 26 Dec 2018 03:08:16 +0700 Subject: fixes nim-lang/nimsuggest#76 (#10093) * nimsuggest: prevent out-of-bound access * fixes nim-lang/nimsuggest#76 * undo tester changes --- compiler/semstmts.nim | 12 +++++++++++- nimsuggest/nimsuggest.nim | 4 ++-- nimsuggest/tests/ttype_highlight.nim | 28 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 nimsuggest/tests/ttype_highlight.nim diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 4b714e1f3..9016fed2b 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -315,7 +315,17 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym = result = semIdentWithPragma(c, kind, n, {}) if result.owner.kind == skModule: incl(result.flags, sfGlobal) - suggestSym(c.config, n.info, result, c.graph.usageSym) + let info = case n.kind + of nkPostfix: + n.sons[1].info + of nkPragmaExpr: + if n.sons[0].kind == nkPostfix: + n.sons[0].sons[1].info + else: + n.sons[0].info + else: + n.info + suggestSym(c.config, info, result, c.graph.usageSym) proc checkNilable(c: PContext; v: PSym) = if {sfGlobal, sfImportC} * v.flags == {sfGlobal} and diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim index c8c6101d7..c24a8bafa 100644 --- a/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -99,7 +99,7 @@ type proc parseQuoted(cmd: string; outp: var string; start: int): int = var i = start i += skipWhitespace(cmd, i) - if cmd[i] == '"': + if i < cmd.len and cmd[i] == '"': i += parseUntil(cmd, outp, '"', i+1)+2 else: i += parseUntil(cmd, outp, seps, i) @@ -428,7 +428,7 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) = var dirtyfile = "" var orig = "" i = parseQuoted(cmd, orig, i) - if cmd[i] == ';': + if i < cmd.len and cmd[i] == ';': i = parseQuoted(cmd, dirtyfile, i+1) i += skipWhile(cmd, seps, i) var line = -1 diff --git a/nimsuggest/tests/ttype_highlight.nim b/nimsuggest/tests/ttype_highlight.nim new file mode 100644 index 000000000..e4189a015 --- /dev/null +++ b/nimsuggest/tests/ttype_highlight.nim @@ -0,0 +1,28 @@ +type + TypeA = int + TypeB* = int + TypeC {.unchecked.} = array[1, int] + TypeD[T] = T + TypeE* {.unchecked.} = array[0, int]#[!]# + +discard """ +disabled:true +$nimsuggest --tester $file +>highlight $1 +highlight;;skType;;2;;2;;5 +highlight;;skType;;3;;2;;5 +highlight;;skType;;4;;2;;5 +highlight;;skType;;5;;2;;5 +highlight;;skType;;6;;2;;5 +highlight;;skType;;2;;10;;3 +highlight;;skType;;3;;11;;3 +highlight;;skType;;4;;24;;5 +highlight;;skType;;4;;33;;3 +highlight;;skType;;5;;13;;1 +highlight;;skType;;6;;25;;5 +highlight;;skType;;6;;34;;3 +highlight;;skType;;2;;10;;3 +highlight;;skType;;3;;11;;3 +highlight;;skType;;4;;33;;3 +highlight;;skType;;6;;34;;3 +""" -- cgit 1.4.1-2-gfad0 From 65a52ecebb98774777f1792f5696b211c51926af Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Thu, 27 Dec 2018 05:20:33 +0530 Subject: Fix const tuple unpacking not working for variables > 2 (#10099) --- compiler/semstmts.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 9016fed2b..04991193c 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -580,7 +580,7 @@ proc semConst(c: PContext, n: PNode): PNode = if a.kind == nkVarTuple: if typ.kind != tyTuple: localError(c.config, a.info, errXExpected, "tuple") - elif int(length/2) != sonsLen(typ): + elif length-2 != sonsLen(typ): localError(c.config, a.info, errWrongNumberOfVariables) b = newNodeI(nkVarTuple, a.info) newSons(b, length) -- cgit 1.4.1-2-gfad0 From 513a287c61e6e461b013e75a7f5dbfa0136b606c Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Thu, 27 Dec 2018 10:55:21 +0000 Subject: Revert sub-second randomize(). Fixes randomize for JS backend. (#10000) Fixes #9999. --- lib/pure/random.nim | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/pure/random.nim b/lib/pure/random.nim index d6501c87e..26e6740ea 100644 --- a/lib/pure/random.nim +++ b/lib/pure/random.nim @@ -224,8 +224,12 @@ when not defined(nimscript): proc randomize*() {.benign.} = ## Initializes the random number generator with a "random" ## number, i.e. a tickcount. Note: Does not work for NimScript. - let now = times.getTime() - randomize(convert(Seconds, Nanoseconds, now.toUnix) + now.nanosecond) + when defined(js): + let time = int64(times.epochTime() * 1_000_000_000) + randomize(time) + else: + let now = times.getTime() + randomize(convert(Seconds, Nanoseconds, now.toUnix) + now.nanosecond) {.pop.} -- cgit 1.4.1-2-gfad0 From e3cee541bd74cc9acc6b3682afd94eb17736f6ae Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Thu, 27 Dec 2018 21:26:48 +0000 Subject: Fixes long-standing asynchttpserver regression. (#10102) --- lib/pure/asynchttpserver.nim | 48 +++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index d27c2fb9c..e3fc75597 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -143,11 +143,14 @@ proc parseUppercaseMethod(name: string): HttpMethod = of "TRACE": HttpTrace else: raise newException(ValueError, "Invalid HTTP method " & name) -proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], - client: AsyncSocket, - address: string, lineFut: FutureVar[string], - callback: proc (request: Request): - Future[void] {.closure, gcsafe.}) {.async.} = +proc processRequest( + server: AsyncHttpServer, + req: FutureVar[Request], + client: AsyncSocket, + address: string, + lineFut: FutureVar[string], + callback: proc (request: Request): Future[void] {.closure, gcsafe.}, +): Future[bool] {.async.} = # Alias `request` to `req.mget()` so we don't have to write `mget` everywhere. template request(): Request = @@ -171,12 +174,12 @@ proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], if lineFut.mget == "": client.close() - return + return false if lineFut.mget.len > maxLine: await request.respondError(Http413) client.close() - return + return false if lineFut.mget != "\c\L": break @@ -189,22 +192,22 @@ proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], request.reqMethod = parseUppercaseMethod(linePart) except ValueError: asyncCheck request.respondError(Http400) - return + return true # Retry processing of request of 1: try: parseUri(linePart, request.url) except ValueError: asyncCheck request.respondError(Http400) - return + return true of 2: try: request.protocol = parseProtocol(linePart) except ValueError: asyncCheck request.respondError(Http400) - return + return true else: await request.respondError(Http400) - return + return true inc i # Headers @@ -215,10 +218,10 @@ proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], await client.recvLineInto(lineFut, maxLength=maxLine) if lineFut.mget == "": - client.close(); return + client.close(); return false if lineFut.mget.len > maxLine: await request.respondError(Http413) - client.close(); return + client.close(); return false if lineFut.mget == "\c\L": break let (key, value) = parseHeader(lineFut.mget) request.headers[key] = value @@ -226,7 +229,7 @@ proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], if request.headers.len > headerLimit: await client.sendStatus("400 Bad Request") request.client.close() - return + return false if request.reqMethod == HttpPost: # Check for Expect header @@ -242,24 +245,24 @@ proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], var contentLength = 0 if parseSaturatedNatural(request.headers["Content-Length"], contentLength) == 0: await request.respond(Http400, "Bad Request. Invalid Content-Length.") - return + return true else: if contentLength > server.maxBody: await request.respondError(Http413) - return + return false request.body = await client.recv(contentLength) if request.body.len != contentLength: await request.respond(Http400, "Bad Request. Content-Length does not match actual.") - return + return true elif request.reqMethod == HttpPost: await request.respond(Http411, "Content-Length required.") - return + return true # Call the user's callback. await callback(request) if "upgrade" in request.headers.getOrDefault("connection"): - return + return false # Persistent connections if (request.protocol == HttpVer11 and @@ -273,7 +276,7 @@ proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], discard else: request.client.close() - return + return false proc processClient(server: AsyncHttpServer, client: AsyncSocket, address: string, callback: proc (request: Request): @@ -285,7 +288,10 @@ proc processClient(server: AsyncHttpServer, client: AsyncSocket, address: string lineFut.mget() = newStringOfCap(80) while not client.isClosed: - await processRequest(server, request, client, address, lineFut, callback) + let retry = await processRequest( + server, request, client, address, lineFut, callback + ) + if not retry: break proc serve*(server: AsyncHttpServer, port: Port, callback: proc (request: Request): Future[void] {.closure,gcsafe.}, -- cgit 1.4.1-2-gfad0 From ca0ef62c9e3cd18d93b7aec6fd33f7eb9f3173e3 Mon Sep 17 00:00:00 2001 From: alaviss Date: Fri, 28 Dec 2018 04:27:23 +0700 Subject: config/nim.cfg: disable tlsEmulation on Haiku (#10092) [backport] As of hrev52662, TLS support in Haiku is usable again. This has since been backported to R1/Beta1, so we can flip the switch upstream. --- config/nim.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/config/nim.cfg b/config/nim.cfg index b52c71c94..2a118c5cf 100644 --- a/config/nim.cfg +++ b/config/nim.cfg @@ -102,9 +102,6 @@ path="$lib/pure" tlsEmulation:on @end @if haiku: - # Haiku currently have problems with TLS - # https://dev.haiku-os.org/ticket/14342 - tlsEmulation:on gcc.options.linker = "-Wl,--as-needed -lnetwork" gcc.cpp.options.linker = "-Wl,--as-needed -lnetwork" clang.options.linker = "-Wl,--as-needed -lnetwork" -- cgit 1.4.1-2-gfad0 From 89488947137277efec2d6bc576843de84cf29e7b Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Thu, 27 Dec 2018 13:28:08 -0800 Subject: [ci skip] update docs for build_all.sh (#10086) --- build_all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_all.sh b/build_all.sh index a268cb791..6c28e7407 100644 --- a/build_all.sh +++ b/build_all.sh @@ -26,6 +26,6 @@ build_nim_csources(){ [ -f $nim_csources ] || echo_run build_nim_csources -echo_run bin/nim c koch +echo_run bin/nim c koch # Note: if fails, may need to `cd csources && git pull` echo_run ./koch boot -d:release echo_run ./koch tools # Compile Nimble and other tools. -- cgit 1.4.1-2-gfad0 From 05b8085a873ad0d8e5e823d35a0b043997e873df Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Fri, 28 Dec 2018 03:08:24 +0530 Subject: Pragma syntax is now consistent (#9993) * Give deprecation warning when type pragmas do not follow the type name * pragma before generic parameter list in type definition is now deprecated * Update changelog * Fix bug where deprecated warning was being shown even though no generic param list was present * Fix bug * Use errGenerated * Best attempt at writing the grammar --- changelog.md | 6 ++++++ compiler/parser.nim | 37 ++++++++++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index d25b6101d..47642cab5 100644 --- a/changelog.md +++ b/changelog.md @@ -141,6 +141,12 @@ proc enumToString*(enums: openArray[enum]): string = it's more recognizable and allows tools like github to recognize it as Nim, see [#9647](https://github.com/nim-lang/Nim/issues/9647). The previous extension will continue to work. +- Pragma syntax is now consistent. Previous syntax where type pragmas did not + follow the type name is now deprecated. Also pragma before generic parameter + list is deprecated to be consistent with how pragmas are used with a proc. See + [#8514](https://github.com/nim-lang/Nim/issues/8514) and + [#1872](https://github.com/nim-lang/Nim/issues/1872) for further details. + ### Tool changes - `jsondoc` now include a `moduleDescription` field with the module diff --git a/compiler/parser.nim b/compiler/parser.nim index e26ea5ee2..c2c8427c8 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -1905,6 +1905,8 @@ proc parseObject(p: var TParser): PNode = result = newNodeP(nkObjectTy, p) getTok(p) if p.tok.tokType == tkCurlyDotLe and p.validInd: + # Deprecated since v0.20.0 + parMessage(p, warnDeprecated, "type pragmas follow the type name; this form of writing pragmas") addSon(result, parsePragma(p)) else: addSon(result, p.emptyNode) @@ -1977,13 +1979,42 @@ proc parseTypeClass(p: var TParser): PNode = proc parseTypeDef(p: var TParser): PNode = #| #| typeDef = identWithPragmaDot genericParamList? '=' optInd typeDefAux + #| indAndComment? / identVisDot genericParamList? pragma '=' optInd typeDefAux #| indAndComment? result = newNodeP(nkTypeDef, p) - addSon(result, identWithPragma(p, allowDot=true)) + var identifier = identVis(p, allowDot=true) + var identPragma = identifier + var pragma: PNode + var genericParam: PNode + var noPragmaYet = true + + if p.tok.tokType == tkCurlyDotLe: + pragma = optPragmas(p) + identPragma = newNodeP(nkPragmaExpr, p) + addSon(identPragma, identifier) + addSon(identPragma, pragma) + noPragmaYet = false + if p.tok.tokType == tkBracketLe and p.validInd: - addSon(result, parseGenericParamList(p)) + if not noPragmaYet: + # Deprecated since v0.20.0 + parMessage(p, warnDeprecated, "pragma before generic parameter list") + genericParam = parseGenericParamList(p) else: - addSon(result, p.emptyNode) + genericParam = p.emptyNode + + if noPragmaYet: + pragma = optPragmas(p) + if pragma.kind != nkEmpty: + identPragma = newNodeP(nkPragmaExpr, p) + addSon(identPragma, identifier) + addSon(identPragma, pragma) + elif p.tok.tokType == tkCurlyDotLe: + parMessage(p, errGenerated, "pragma already present") + + addSon(result, identPragma) + addSon(result, genericParam) + if p.tok.tokType == tkEquals: result.info = parLineInfo(p) getTok(p) -- cgit 1.4.1-2-gfad0 From 1b7c8482fef98a9e1c0e4a61e7e928a3e4890192 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Thu, 27 Dec 2018 13:40:09 -0800 Subject: fixes #10101 (#10103) --- compiler/lookups.nim | 2 +- tests/errmsgs/treportunused.nim | 8 ++++---- tests/pragmas/tused.nim | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/lookups.nim b/compiler/lookups.nim index db03ac2e0..11a741505 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -174,7 +174,7 @@ proc ensureNoMissingOrUnusedSymbols(c: PContext; scope: PScope) = # maybe they can be made skGenericParam as well. if s.typ != nil and tfImplicitTypeParam notin s.typ.flags and s.typ.kind != tyGenericParam: - message(c.config, s.info, hintXDeclaredButNotUsed, getSymRepr(c.config, s)) + message(c.config, s.info, hintXDeclaredButNotUsed, s.name.s) s = nextIter(it, scope.symbols) proc wrongRedefinition*(c: PContext; info: TLineInfo, s: string; diff --git a/tests/errmsgs/treportunused.nim b/tests/errmsgs/treportunused.nim index c74fea46f..b339e06bf 100644 --- a/tests/errmsgs/treportunused.nim +++ b/tests/errmsgs/treportunused.nim @@ -1,12 +1,12 @@ discard """ nimout: ''' -treportunused.nim(19, 10) Hint: 'treportunused.s1(a: string)[declared in treportunused.nim(19, 9)]' is declared but not used [XDeclaredButNotUsed] +treportunused.nim(19, 10) Hint: 's1' is declared but not used [XDeclaredButNotUsed] treportunused.nim(26, 5) Hint: 's8' is declared but not used [XDeclaredButNotUsed] -treportunused.nim(22, 6) Hint: 'treportunused.s4()[declared in treportunused.nim(22, 5)]' is declared but not used [XDeclaredButNotUsed] +treportunused.nim(22, 6) Hint: 's4' is declared but not used [XDeclaredButNotUsed] treportunused.nim(25, 7) Hint: 's7' is declared but not used [XDeclaredButNotUsed] treportunused.nim(24, 7) Hint: 's6' is declared but not used [XDeclaredButNotUsed] -treportunused.nim(23, 6) Hint: 'treportunused.s5(a: T)[declared in treportunused.nim(23, 5)]' is declared but not used [XDeclaredButNotUsed] -treportunused.nim(20, 10) Hint: 'treportunused.s2()[declared in treportunused.nim(20, 9)]' is declared but not used [XDeclaredButNotUsed] +treportunused.nim(23, 6) Hint: 's5' is declared but not used [XDeclaredButNotUsed] +treportunused.nim(20, 10) Hint: 's2' is declared but not used [XDeclaredButNotUsed] treportunused.nim(29, 6) Hint: 's11' is declared but not used [XDeclaredButNotUsed] treportunused.nim(27, 5) Hint: 's9' is declared but not used [XDeclaredButNotUsed] treportunused.nim(21, 10) Hint: 's3' is declared but not used [XDeclaredButNotUsed] diff --git a/tests/pragmas/tused.nim b/tests/pragmas/tused.nim index 7616c1215..d0c533f9a 100644 --- a/tests/pragmas/tused.nim +++ b/tests/pragmas/tused.nim @@ -1,7 +1,7 @@ discard """ nimout: ''' compile start -tused.nim(17, 8) Hint: 'tused.echoSub(a: int, b: int)[declared in tused.nim(17, 7)]' is declared but not used [XDeclaredButNotUsed] +tused.nim(17, 8) Hint: 'echoSub' is declared but not used [XDeclaredButNotUsed] compile end''' output: "8\n8" joinable: false -- cgit 1.4.1-2-gfad0 From cc4720fac18ba0d9bacdb386cacb409632a5e1a6 Mon Sep 17 00:00:00 2001 From: ee7 <45465154+ee7@users.noreply.github.com> Date: Thu, 27 Dec 2018 22:40:40 +0100 Subject: Const tuple unpacking: add tests (#10100) --- tests/tuples/ttuples_various.nim | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/tuples/ttuples_various.nim b/tests/tuples/ttuples_various.nim index 010893ced..94a2f3ff0 100644 --- a/tests/tuples/ttuples_various.nim +++ b/tests/tuples/ttuples_various.nim @@ -66,6 +66,22 @@ block unpack_asgn: +block unpack_const: + const (a, ) = (1, ) + doAssert a == 1 + + const (b, c) = (2, 3) + doAssert b == 2 + doAssert c == 3 + + # bug #10098 + const (x, y, z) = (4, 5, 6) + doAssert x == 4 + doAssert y == 5 + doAssert z == 6 + + + block tuple_subscript: proc`[]` (t: tuple, key: string): string = for name, field in fieldPairs(t): -- cgit 1.4.1-2-gfad0 From eba8ffcf70cca5dd802c5d33f6ecea814829f9fc Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Thu, 27 Dec 2018 13:46:53 -0800 Subject: `checkErr` now shows actual system error msg instead of unknown error (#9987) --- lib/system/sysio.nim | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim index 20964b166..5b0278d74 100644 --- a/lib/system/sysio.nim +++ b/lib/system/sysio.nim @@ -74,10 +74,21 @@ proc raiseEIO(msg: string) {.noinline, noreturn.} = proc raiseEOF() {.noinline, noreturn.} = sysFatal(EOFError, "EOF reached") +proc strerror(errnum: cint): cstring {.importc, header: "".} + +when not defined(NimScript): + var + errno {.importc, header: "".}: cint ## error variable + proc checkErr(f: File) = - if c_ferror(f) != 0: - c_clearerr(f) - raiseEIO("Unknown IO Error") + when not defined(NimScript): + if c_ferror(f) != 0: + let msg = "errno: " & $errno & " `" & $strerror(errno) & "`" + c_clearerr(f) + raiseEIO(msg) + else: + # shouldn't happen + quit(1) {.push stackTrace:off, profiler:off.} proc readBuffer(f: File, buffer: pointer, len: Natural): int = -- cgit 1.4.1-2-gfad0 From 083129286349ba440018cff1ed20172b675b84fe Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Sat, 29 Dec 2018 16:09:47 -0800 Subject: revives: Move typetraits.`$` to system. Fixes #5827 (#10071) * Move typetraits.`$` to system. Fixes #5827. * revive PR; adjust code to make sure everything works and add tests * fix tests/concepts/tstackconcept.nim * address comments --- compiler/semmagic.nim | 2 +- lib/pure/typetraits.nim | 44 ++++++++++++++++++++----------------------- lib/system.nim | 9 +++++++++ tests/system/tsystem_misc.nim | 14 ++++++++++++++ 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 7e61854b8..568b418e9 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -136,7 +136,7 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym) return typeWithSonsResult(tyAnd, @[operand, operand2]) of "not": return typeWithSonsResult(tyNot, @[operand]) - of "name": + of "name", "$": result = newStrNode(nkStrLit, operand.typeToString(preferTypeName)) result.typ = newType(tyString, context) result.info = traitCall.info diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim index c53b68864..db57ac02d 100644 --- a/lib/pure/typetraits.nim +++ b/lib/pure/typetraits.nim @@ -11,29 +11,7 @@ ## working with types proc name*(t: typedesc): string {.magic: "TypeTrait".} - ## Returns the name of the given type. - ## - ## Example: - ## - ## .. code-block:: - ## - ## import typetraits - ## - ## proc `$`*(T: typedesc): string = name(T) - ## - ## template test(x): typed = - ## echo "type: ", type(x), ", value: ", x - ## - ## test 42 - ## # --> type: int, value: 42 - ## test "Foo" - ## # --> type: string, value: Foo - ## test(@['A','B']) - ## # --> type: seq[char], value: @[A, B] - -proc `$`*(t: typedesc): string = - ## An alias for `name`. - name(t) + ## Alias for system.`$`(t) since Nim v0.20.0. proc arity*(t: typedesc): int {.magic: "TypeTrait".} = ## Returns the arity of the given type. This is the number of "type" components or @@ -64,4 +42,22 @@ proc supportsCopyMem*(t: typedesc): bool {.magic: "TypeTrait".} when isMainModule: - doAssert $type(42) == "int" + static: + doAssert $type(42) == "int" + doAssert int.name == "int" + + const a1 = name(int) + const a2 = $(int) + const a3 = $int + doAssert a1 == "int" + doAssert a2 == "int" + doAssert a3 == "int" + + proc fun[T: typedesc](t: T) = + const a1 = name(t) + const a2 = $(t) + const a3 = $t + doAssert a1 == "int" + doAssert a2 == "int" + doAssert a3 == "int" + fun(int) diff --git a/lib/system.nim b/lib/system.nim index 761794da6..bcd5fe05a 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -4431,3 +4431,12 @@ when defined(genode): componentConstructHook(env) # Perform application initialization # and return to thread entrypoint. + +proc `$`*(t: typedesc): string {.magic: "TypeTrait".} = + ## Returns the name of the given type. + ## + ## For more procedures dealing with ``typedesc``, see ``typetraits.nim``. + runnableExamples: + doAssert $(type(42)) == "int" + doAssert $(type("Foo")) == "string" + static: doAssert $(type(@['A', 'B'])) == "seq[char]" diff --git a/tests/system/tsystem_misc.nim b/tests/system/tsystem_misc.nim index 3bbb5eff1..98bc3f4a3 100644 --- a/tests/system/tsystem_misc.nim +++ b/tests/system/tsystem_misc.nim @@ -30,6 +30,20 @@ discard """ ''' """ + +block: + const a2 = $(int) + const a3 = $int + doAssert a2 == "int" + doAssert a3 == "int" + + proc fun[T: typedesc](t: T) = + const a2 = $(t) + const a3 = $t + doAssert a2 == "int" + doAssert a3 == "int" + fun(int) + # check high/low implementations doAssert high(int) > low(int) doAssert high(int8) > low(int8) -- cgit 1.4.1-2-gfad0 From 9c65ea0d16828a5e13b136aa05348bce15312dc6 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Sat, 29 Dec 2018 16:21:33 -0800 Subject: `nim c` now allows: when defined(c) (#10130) --- compiler/main.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/main.nim b/compiler/main.nim index 2b2907794..54dc8cb23 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -167,6 +167,7 @@ proc mainCommand*(graph: ModuleGraph) = of "c", "cc", "compile", "compiletoc": # compile means compileToC currently conf.cmd = cmdCompileToC + defineSymbol(graph.config.symbols, "c") commandCompileToC(graph) of "cpp", "compiletocpp": conf.cmd = cmdCompileToCpp -- cgit 1.4.1-2-gfad0 From e98d54b050b0fba8d3d76a54e4eb39cd197ee5f1 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Sat, 29 Dec 2018 16:27:37 -0800 Subject: nim dump: add nimcache entry (#10122) --- compiler/main.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/main.nim b/compiler/main.nim index 54dc8cb23..b5f7e8364 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -284,6 +284,7 @@ proc mainCommand*(graph: ModuleGraph) = (key: "defined_symbols", val: definedSymbols), (key: "lib_paths", val: %libpaths), (key: "out", val: %conf.outFile.string), + (key: "nimcache", val: %getNimcacheDir(conf).string), (key: "hints", val: hints), (key: "warnings", val: warnings), ] -- cgit 1.4.1-2-gfad0 From 527b77477292e8dfd3be7779213f1fd7071f2e58 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Sun, 30 Dec 2018 05:58:56 +0530 Subject: {.push raises: [].} is now ignored for vars/lets/consts (#10026) --- compiler/pragmas.nim | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index f67e74f11..78021667e 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -64,10 +64,10 @@ const varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl, wMagic, wHeader, wDeprecated, wCompilerProc, wCore, wDynlib, wExtern, wImportCpp, wImportObjC, wError, wNoInit, wCompileTime, wGlobal, - wGensym, wInject, wCodegenDecl, wGuard, wGoto, wExportNims, wUsed} + wGensym, wInject, wCodegenDecl, wGuard, wGoto, wExportNims, wUsed, wRaises} constPragmas* = {wImportc, wExportc, wHeader, wDeprecated, wMagic, wNodecl, wExtern, wImportCpp, wImportObjC, wError, wGensym, wInject, wExportNims, - wIntDefine, wStrDefine, wUsed, wCompilerProc, wCore} + wIntDefine, wStrDefine, wUsed, wCompilerProc, wCore, wRaises} letPragmas* = varPragmas procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNosideeffect, wThread, wRaises, wLocks, wTags, wGcSafe} @@ -740,7 +740,7 @@ proc semCustomPragma(c: PContext, n: PNode): PNode = result.kind = n.kind # pragma(arg) -> pragma: arg proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, - validPragmas: TSpecialWords): bool = + validPragmas: TSpecialWords, comesFromPush: bool) : bool = var it = n.sons[i] var key = if it.kind in nkPragmaCallKinds and it.len > 1: it.sons[0] else: it if key.kind == nkBracketExpr: @@ -1053,7 +1053,14 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, noVal(c, it) if sym == nil: invalidPragma(c, it) of wLine: pragmaLine(c, it) - of wRaises, wTags: pragmaRaisesOrTags(c, it) + of wRaises, wTags: + if not sym.isNil and sym.kind in {skVar, skLet, skConst}: + if comesFromPush: + return + else: + invalidPragma(c, it) + else: + pragmaRaisesOrTags(c, it) of wLocks: if sym == nil: pragmaLockStmt(c, it) elif sym.typ == nil: invalidPragma(c, it) @@ -1134,7 +1141,7 @@ proc implicitPragmas*(c: PContext, sym: PSym, n: PNode, pushInfoContext(c.config, n.info) var i = 0 while i < o.len: - if singlePragma(c, sym, o, i, validPragmas): + if singlePragma(c, sym, o, i, validPragmas, true): internalError(c.config, n.info, "implicitPragmas") inc i popInfoContext(c.config) @@ -1163,7 +1170,7 @@ proc pragmaRec(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) = if n == nil: return var i = 0 while i < n.len: - if singlePragma(c, sym, n, i, validPragmas): break + if singlePragma(c, sym, n, i, validPragmas, false): break inc i proc pragma(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) = -- cgit 1.4.1-2-gfad0 From 7f81195e5a2e423d09261fbdd83612bf0c4c909a Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Sat, 29 Dec 2018 16:31:30 -0800 Subject: document --profiler:on (#10115) --- doc/advopt.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/advopt.txt b/doc/advopt.txt index 60cae7fd0..7445068c1 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -108,3 +108,6 @@ Advanced options: --experimental:$1 enable experimental language feature -v, --version show detailed version information + --profiler:on|off Enable profiling; requires `import nimprof`, and + works better with `--stackTrace:on` + see also https://nim-lang.github.io/Nim/estp.html -- cgit 1.4.1-2-gfad0 From dd33f418779c1476b6d8199f72611e282f889199 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Sat, 29 Dec 2018 17:06:15 -0800 Subject: refs #10121 (#10124) --- lib/pure/times.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 7afc3dade..0a06d5f9f 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -163,7 +163,8 @@ when defined(posix): when not defined(freebsd) and not defined(netbsd) and not defined(openbsd): var timezone {.importc, header: "".}: int - tzset() + when not defined(valgrind_workaround_10121): + tzset() elif defined(windows): import winlean -- cgit 1.4.1-2-gfad0 From 062d7e3a398f7afb039ce568cceb32e908e3340e Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Sun, 30 Dec 2018 02:45:34 +0100 Subject: fixes #10082 --- compiler/sizealignoffsetimpl.nim | 36 +++++++++++------------------------- tests/misc/tsizeof.nim | 11 +++++++++++ 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/compiler/sizealignoffsetimpl.nim b/compiler/sizealignoffsetimpl.nim index a34383d9f..ea4730f57 100644 --- a/compiler/sizealignoffsetimpl.nim +++ b/compiler/sizealignoffsetimpl.nim @@ -18,7 +18,7 @@ const szIllegalRecursion* = -2 szUncomputedSize* = -1 -proc computeSizeAlign(conf: ConfigRef; typ: PType): void +proc computeSizeAlign(conf: ConfigRef; typ: PType) proc computeSubObjectAlign(conf: ConfigRef; n: PNode): BiggestInt = ## returns object alignment @@ -49,13 +49,14 @@ proc computeSubObjectAlign(conf: ConfigRef; n: PNode): BiggestInt = else: result = 1 -proc computeObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, initialOffset: BiggestInt): tuple[offset, align: BiggestInt] = +proc computeObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, + initialOffset: BiggestInt): tuple[offset, align: BiggestInt] = ## ``offset`` is the offset within the object, after the node has been written, no padding bytes added ## ``align`` maximum alignment from all sub nodes assert n != nil if n.typ != nil and n.typ.size == szIllegalRecursion: result.offset = szIllegalRecursion - result.align = szIllegalRecursion + result.align = szIllegalRecursion return result.align = 1 @@ -71,66 +72,52 @@ proc computeObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, initialOffset: of nkOfBranch, nkElse: # offset parameter cannot be known yet, it needs to know the alignment first let align = computeSubObjectAlign(conf, n.sons[i].lastSon) - if align == szIllegalRecursion: - result.offset = szIllegalRecursion + result.offset = szIllegalRecursion result.align = szIllegalRecursion return - if align == szUnknownSize or maxChildAlign == szUnknownSize: maxChildAlign = szUnknownSize else: maxChildAlign = max(maxChildAlign, align) else: internalError(conf, "computeObjectOffsetsFoldFunction(record case branch)") - if maxChildAlign == szUnknownSize: result.align = szUnknownSize result.offset = szUnknownSize else: # the union neds to be aligned first, before the offsets can be assigned let kindUnionOffset = align(kindOffset, maxChildAlign) - var maxChildOffset: BiggestInt = 0 for i in 1 ..< sonsLen(n): let (offset, align) = computeObjectOffsetsFoldFunction(conf, n.sons[i].lastSon, kindUnionOffset) maxChildOffset = max(maxChildOffset, offset) - result.align = max(kindAlign, maxChildAlign) result.offset = maxChildOffset - - of nkRecList: result.align = 1 # maximum of all member alignments var offset = initialOffset - for i, child in n.sons: let (new_offset, align) = computeObjectOffsetsFoldFunction(conf, child, offset) - if new_offset == szIllegalRecursion: result.offset = szIllegalRecursion result.align = szIllegalRecursion return - elif new_offset == szUnknownSize or offset == szUnknownSize: # if anything is unknown, the rest becomes unknown as well offset = szUnknownSize result.align = szUnknownSize - else: offset = new_offset result.align = max(result.align, align) - # final alignment if offset == szUnknownSize: result.offset = szUnknownSize else: result.offset = align(offset, result.align) - of nkSym: var size = szUnknownSize var align = szUnknownSize - if n.sym.bitsize == 0: # 0 represents bitsize not set computeSizeAlign(conf, n.sym.typ) size = n.sym.typ.size.int @@ -155,7 +142,6 @@ proc computePackedObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, initialOf let kindOffset = computePackedObjectOffsetsFoldFunction(conf, n.sons[0], initialOffset, debug) # the union neds to be aligned first, before the offsets can be assigned let kindUnionOffset = kindOffset - var maxChildOffset: BiggestInt = kindUnionOffset for i in 1 ..< sonsLen(n): let offset = computePackedObjectOffsetsFoldFunction(conf, n.sons[i].lastSon, kindUnionOffset, debug) @@ -168,9 +154,12 @@ proc computePackedObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, initialOf if result == szIllegalRecursion: break of nkSym: - computeSizeAlign(conf, n.sym.typ) - n.sym.offset = initialOffset.int - result = n.sym.offset + n.sym.typ.size + if n.sym.bitsize == 0: + computeSizeAlign(conf, n.sym.typ) + n.sym.offset = initialOffset.int + result = n.sym.offset + n.sym.typ.size + else: + result = szUnknownSize else: result = szUnknownSize @@ -324,7 +313,6 @@ proc computeSizeAlign(conf: ConfigRef; typ: PType) = var headerAlign: int16 if typ.sons[0] != nil: # compute header size - if conf.cmd == cmdCompileToCpp: # if the target is C++ the members of this type are written # into the padding byets at the end of the parent type. At the @@ -364,7 +352,6 @@ proc computeSizeAlign(conf: ConfigRef; typ: PType) = typ.size = szUnknownSize typ.align = szUnknownSize return - # header size is already in size from computeObjectOffsetsFoldFunction # maxAlign is probably not changed at all from headerAlign if tfPacked in typ.flags: @@ -373,7 +360,6 @@ proc computeSizeAlign(conf: ConfigRef; typ: PType) = else: typ.align = int16(max(align, headerAlign)) typ.size = align(offset, typ.align) - of tyInferred: if typ.len > 1: computeSizeAlign(conf, typ.lastSon) diff --git a/tests/misc/tsizeof.nim b/tests/misc/tsizeof.nim index a73b3dcde..4422e900e 100644 --- a/tests/misc/tsizeof.nim +++ b/tests/misc/tsizeof.nim @@ -391,6 +391,17 @@ type assert sizeof(Bar) == 12 +# bug #10082 +type + A = int8 # change to int16 and get sizeof(C)==6 + B = int16 + C = object {.packed.} + d {.bitsize: 1.}: A + e {.bitsize: 7.}: A + f {.bitsize: 16.}: B + +assert sizeof(C) == 3 + if failed: quit("FAIL") else: -- cgit 1.4.1-2-gfad0 From aadbdd6b066bc7251c6d24bb5ef5ce3f940825ec Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Sun, 30 Dec 2018 03:48:37 +0200 Subject: Support undefined in isNil (#9960) --- compiler/jsgen.nim | 7 ++++--- lib/js/jsffi.nim | 6 ++++++ tests/js/tjsffi.nim | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index daaa145e1..83d205bc2 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -1884,12 +1884,13 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = of mLtStr: binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) < 0)") of mIsNil: + # we want to accept undefined, so we == if mapType(n[1].typ) != etyBaseIndex: - unaryExpr(p, n, r, "", "($1 === null)") + unaryExpr(p, n, r, "", "($1 == null)") else: var x: TCompRes gen(p, n[1], x) - r.res = "($# === null && $# === 0)" % [x.address, x.res] + r.res = "($# == null && $# === 0)" % [x.address, x.res] of mEnumToStr: genRepr(p, n, r) of mNew, mNewFinalize: genNew(p, n) of mChr: gen(p, n.sons[1], r) @@ -1922,7 +1923,7 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 -= $2") else: binaryExpr(p, n, r, "subInt", "$1 = subInt($3, $2)") of mSetLengthStr: - binaryExpr(p, n, r, "mnewString", "($1 === null ? $3 = mnewString($2) : $3.length = $2)") + binaryExpr(p, n, r, "mnewString", "($1 == null ? $3 = mnewString($2) : $3.length = $2)") of mSetLengthSeq: var x, y: TCompRes gen(p, n.sons[1], x) diff --git a/lib/js/jsffi.nim b/lib/js/jsffi.nim index e1c59803d..7f0939b08 100644 --- a/lib/js/jsffi.nim +++ b/lib/js/jsffi.nim @@ -104,6 +104,12 @@ var jsFilename* {.importc: "__filename", nodecl.}: cstring ## JavaScript's __filename pseudo-variable +proc isNull*[T](x: T): bool {.noSideEffect, importcpp: "(# === null)".} + ## check if a value is exactly null + +proc isUndefined*[T](x: T): bool {.noSideEffect, importcpp: "(# === undefined)".} + ## check if a value is exactly undefined + # Exceptions type JsError* {.importc: "Error".} = object of JsRoot diff --git a/tests/js/tjsffi.nim b/tests/js/tjsffi.nim index 2420c60f6..8bd40a3c4 100644 --- a/tests/js/tjsffi.nim +++ b/tests/js/tjsffi.nim @@ -22,6 +22,13 @@ true Event { name: 'click: test' } Event { name: 'reloaded: test' } Event { name: 'updates: test' } +true +true +true +true +true +true +true ''' """ @@ -317,3 +324,12 @@ block: jslib.subscribe("updates"): console.log jsarguments[0] +block: + + echo jsUndefined == jsNull + echo jsUndefined == nil + echo jsNull == nil + echo jsUndefined.isNil + echo jsNull.isNil + echo jsNull.isNull + echo jsUndefined.isUndefined -- cgit 1.4.1-2-gfad0 From a6633b965891a7f5e70ac6fcf41d4142145b69c2 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Sun, 30 Dec 2018 00:40:21 -0800 Subject: fix typetraits.`$` regression https://github.com/c-blake/cligen/issues/84 (#10131) * fix typetraits.`$` regression https://github.com/c-blake/cligen/issues/84 * add test --- compiler/semmagic.nim | 5 +++-- lib/pure/typetraits.nim | 2 ++ tests/metatype/ttypetraits2.nim | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 tests/metatype/ttypetraits2.nim diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 568b418e9..05c8b181c 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -129,7 +129,8 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym) template typeWithSonsResult(kind, sons): PNode = newTypeWithSons(context, kind, sons).toNode(traitCall.info) - case trait.sym.name.s + let s = trait.sym.name.s + case s of "or", "|": return typeWithSonsResult(tyOr, @[operand, operand2]) of "and": @@ -160,7 +161,7 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym) hasDestructor(t) result = newIntNodeT(ord(not complexObj), traitCall, c.graph) else: - localError(c.config, traitCall.info, "unknown trait") + localError(c.config, traitCall.info, "unknown trait: " & s) result = newNodeI(nkEmpty, traitCall.info) proc semTypeTraits(c: PContext, n: PNode): PNode = diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim index db57ac02d..5f5bfdbd7 100644 --- a/lib/pure/typetraits.nim +++ b/lib/pure/typetraits.nim @@ -10,6 +10,8 @@ ## This module defines compile-time reflection procs for ## working with types +export system.`$` + proc name*(t: typedesc): string {.magic: "TypeTrait".} ## Alias for system.`$`(t) since Nim v0.20.0. diff --git a/tests/metatype/ttypetraits2.nim b/tests/metatype/ttypetraits2.nim new file mode 100644 index 000000000..de80e10b1 --- /dev/null +++ b/tests/metatype/ttypetraits2.nim @@ -0,0 +1,3 @@ +# todo: merge with $nimc_D/tests/metatype/ttypetraits.nim (currently disabled) + +from typetraits import `$` # checks fix for https://github.com/c-blake/cligen/issues/84 -- cgit 1.4.1-2-gfad0 From c5ad4c10cb976960a37656a55ad2fdbb0add8861 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Sun, 30 Dec 2018 14:13:59 +0530 Subject: Deprecated pragma is now supported on enum fields (#10113) * {.deprecated.} pragma is now supported for enum fields * Add tests * Simplify code --- compiler/parser.nim | 32 +++++++++++++++++++++----------- compiler/pragmas.nim | 1 + compiler/semtypes.nim | 10 +++++++++- tests/enum/tenumfieldpragma.nim | 22 ++++++++++++++++++++++ tests/enum/tenumfieldpragmanoannot.nim | 10 ++++++++++ 5 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 tests/enum/tenumfieldpragma.nim create mode 100644 tests/enum/tenumfieldpragmanoannot.nim diff --git a/compiler/parser.nim b/compiler/parser.nim index c2c8427c8..163ad6455 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -1765,7 +1765,7 @@ proc parseSection(p: var TParser, kind: TNodeKind, parMessage(p, errIdentifierExpected, p.tok) proc parseEnum(p: var TParser): PNode = - #| enum = 'enum' optInd (symbol optInd ('=' optInd expr COMMENT?)? comma?)+ + #| enum = 'enum' optInd (symbol optPragmas optInd ('=' optInd expr COMMENT?)? comma?)+ result = newNodeP(nkEnumTy, p) getTok(p) addSon(result, p.emptyNode) @@ -1775,25 +1775,35 @@ proc parseEnum(p: var TParser): PNode = while true: var a = parseSymbol(p) if a.kind == nkEmpty: return + + var symPragma = a + var pragma: PNode + if p.tok.tokType == tkCurlyDotLe: + pragma = optPragmas(p) + symPragma = newNodeP(nkPragmaExpr, p) + addSon(symPragma, a) + addSon(symPragma, pragma) + if p.tok.indent >= 0 and p.tok.indent <= p.currInd: - add(result, a) + add(result, symPragma) break + if p.tok.tokType == tkEquals and p.tok.indent < 0: getTok(p) - optInd(p, a) - var b = a - a = newNodeP(nkEnumFieldDef, p) - addSon(a, b) - addSon(a, parseExpr(p)) + optInd(p, symPragma) + var b = symPragma + symPragma = newNodeP(nkEnumFieldDef, p) + addSon(symPragma, b) + addSon(symPragma, parseExpr(p)) if p.tok.indent < 0 or p.tok.indent >= p.currInd: - rawSkipComment(p, a) + rawSkipComment(p, symPragma) if p.tok.tokType == tkComma and p.tok.indent < 0: getTok(p) - rawSkipComment(p, a) + rawSkipComment(p, symPragma) else: if p.tok.indent < 0 or p.tok.indent >= p.currInd: - rawSkipComment(p, a) - addSon(result, a) + rawSkipComment(p, symPragma) + addSon(result, symPragma) if p.tok.indent >= 0 and p.tok.indent <= p.currInd or p.tok.tokType == tkEof: break diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 78021667e..58f64f7b0 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -73,6 +73,7 @@ const wThread, wRaises, wLocks, wTags, wGcSafe} forVarPragmas* = {wInject, wGensym} allRoutinePragmas* = methodPragmas + iteratorPragmas + lambdaPragmas + enumFieldPragmas* = {wDeprecated} proc getPragmaVal*(procAst: PNode; name: TSpecialWord): PNode = let p = procAst[pragmasPos] diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index e0f04c22f..200b247ca 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -80,9 +80,14 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType = if isPure: initStrTable(symbols) var hasNull = false for i in countup(1, sonsLen(n) - 1): + if n.sons[i].kind == nkEmpty: continue case n.sons[i].kind of nkEnumFieldDef: - e = newSymS(skEnumField, n.sons[i].sons[0], c) + if n.sons[i].sons[0].kind == nkPragmaExpr: + e = newSymS(skEnumField, n.sons[i].sons[0].sons[0], c) + pragma(c, e, n.sons[i].sons[0].sons[1], enumFieldPragmas) + else: + e = newSymS(skEnumField, n.sons[i].sons[0], c) var v = semConstExpr(c, n.sons[i].sons[1]) var strVal: PNode = nil case skipTypes(v.typ, abstractInst-{tyTypeDesc}).kind @@ -115,6 +120,9 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType = e = n.sons[i].sym of nkIdent, nkAccQuoted: e = newSymS(skEnumField, n.sons[i], c) + of nkPragmaExpr: + e = newSymS(skEnumField, n.sons[i].sons[0], c) + pragma(c, e, n.sons[i].sons[1], enumFieldPragmas) else: illFormedAst(n[i], c.config) e.typ = result diff --git a/tests/enum/tenumfieldpragma.nim b/tests/enum/tenumfieldpragma.nim new file mode 100644 index 000000000..604a8f019 --- /dev/null +++ b/tests/enum/tenumfieldpragma.nim @@ -0,0 +1,22 @@ +discard """ + nimout: '''tenumfieldpragma.nim(20, 10) Warning: d is deprecated [Deprecated] +tenumfieldpragma.nim(21, 10) Warning: e is deprecated [Deprecated] +tenumfieldpragma.nim(22, 10) Warning: f is deprecated [Deprecated] +''' +""" + +type + A = enum + a + b = "abc" + c = (10, "def") + d {.deprecated.} + e {.deprecated.} = "ghi" + f {.deprecated.} = (20, "jkl") + +var v1 = a +var v2 = b +var v3 = c +var v4 = d +var v5 = e +var v6 = f diff --git a/tests/enum/tenumfieldpragmanoannot.nim b/tests/enum/tenumfieldpragmanoannot.nim new file mode 100644 index 000000000..47f920827 --- /dev/null +++ b/tests/enum/tenumfieldpragmanoannot.nim @@ -0,0 +1,10 @@ +discard """ + errormsg: "annotation to deprecated not supported here" + line: 8 +""" + +type + A = enum + a {.deprecated: "njshd".} + +var v1 = a -- cgit 1.4.1-2-gfad0 From cbbdcb266962df39bbdacb99ccd2a656ce9c3af7 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Sun, 30 Dec 2018 14:15:39 +0530 Subject: Show deprecation warning for fields of a deprecated enum (#10112) * Show deprecation warning for fields of a deprecated enum * Add test --- compiler/suggest.nim | 11 ++++++++--- tests/deprecated/tdeprecated.nim | 18 ++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/compiler/suggest.nim b/compiler/suggest.nim index dfa6e5ddb..f149327ac 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -462,14 +462,17 @@ proc extractPragma(s: PSym): PNode = doAssert result == nil or result.kind == nkPragma proc warnAboutDeprecated(conf: ConfigRef; info: TLineInfo; s: PSym) = - let pragmaNode = extractPragma(s) + let pragmaNode = if s.kind == skEnumField: extractPragma(s.owner) else: extractPragma(s) + let name = + if s.kind == skEnumField: "enum '" & s.owner.name.s & "' which contains field '" & s.name.s & "'" + else: s.name.s if pragmaNode != nil: for it in pragmaNode: if whichPragma(it) == wDeprecated and it.safeLen == 2 and it[1].kind in {nkStrLit..nkTripleStrLit}: - message(conf, info, warnDeprecated, it[1].strVal & "; " & s.name.s) + message(conf, info, warnDeprecated, it[1].strVal & "; " & name) return - message(conf, info, warnDeprecated, s.name.s) + message(conf, info, warnDeprecated, name) proc userError(conf: ConfigRef; info: TLineInfo; s: PSym) = let pragmaNode = extractPragma(s) @@ -486,6 +489,8 @@ proc markUsed(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym) = incl(s.flags, sfUsed) if s.kind == skEnumField and s.owner != nil: incl(s.owner.flags, sfUsed) + if sfDeprecated in s.owner.flags: + incl(s.flags, sfDeprecated) if {sfDeprecated, sfError} * s.flags != {}: if sfDeprecated in s.flags: warnAboutDeprecated(conf, info, s) if sfError in s.flags: userError(conf, info, s) diff --git a/tests/deprecated/tdeprecated.nim b/tests/deprecated/tdeprecated.nim index 955a7f6ad..920f350cc 100644 --- a/tests/deprecated/tdeprecated.nim +++ b/tests/deprecated/tdeprecated.nim @@ -1,9 +1,19 @@ discard """ - nimout: "a is deprecated [Deprecated]" + nimout: '''tdeprecated.nim(10, 3) Warning: a is deprecated [Deprecated] +tdeprecated.nim(17, 11) Warning: asdf; enum 'Foo' which contains field 'a' is deprecated [Deprecated] +''' """ +block: + var + a {.deprecated.}: array[0..11, int] -var - a {.deprecated.}: array[0..11, int] + a[8] = 1 -a[8] = 1 +block t10111: + type + Foo {.deprecated: "asdf" .} = enum + a + + var _ = a + -- cgit 1.4.1-2-gfad0 From 82c009a2cbc5d07ab9a847f1c58228a20efaf219 Mon Sep 17 00:00:00 2001 From: cooldome Date: Sun, 30 Dec 2018 10:28:12 +0000 Subject: Dead code elimination for entire modules and their init procs if empty (#10032) * fixes #9798 * Change order of write modules * Move datInit calls ahead of initStackBottom --- compiler/ccgmerge.nim | 1 + compiler/cgen.nim | 247 ++++++++++++++++++++++------------ compiler/cgendata.nim | 10 +- lib/system.nim | 9 +- tests/ccgbugs/tforward_decl_only.nim | 2 + tests/manyloc/standalone/barebone.nim | 6 +- 6 files changed, 176 insertions(+), 99 deletions(-) diff --git a/compiler/ccgmerge.nim b/compiler/ccgmerge.nim index 144a45816..2d68a198e 100644 --- a/compiler/ccgmerge.nim +++ b/compiler/ccgmerge.nim @@ -31,6 +31,7 @@ const cfsData: "NIM_merge_DATA", cfsProcs: "NIM_merge_PROCS", cfsInitProc: "NIM_merge_INIT_PROC", + cfsDatInitProc: "NIM_merge_DATINIT_PROC", cfsTypeInit1: "NIM_merge_TYPE_INIT1", cfsTypeInit2: "NIM_merge_TYPE_INIT2", cfsTypeInit3: "NIM_merge_TYPE_INIT3", diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 457a6e176..3d76be254 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -1087,28 +1087,23 @@ proc getFileHeader(conf: ConfigRef; cfile: Cfile): Rope = result = getCopyright(conf, cfile) addIntTypes(result, conf) -proc genFilenames(m: BModule): Rope = - discard cgsym(m, "dbgRegisterFilename") - result = nil - for i in 0.. 0: + let init = m.module.getInitName + addf(g.mainModProcs, "N_LIB_PRIVATE N_NIMCALL(void, $1)(void);$N", [init]) let initCall = "\t$1();$N" % [init] - if sfMainModule in m.flags: + if sfMainModule in m.module.flags: add(g.mainModInit, initCall) else: add(g.otherModsInit, initCall) + if m.s[cfsDatInitProc].len > 0: + let datInit = m.module.getDatInitName + addf(g.mainModProcs, "N_LIB_PRIVATE N_NIMCALL(void, $1)(void);$N", [datInit]) + addf(g.mainDatInit, "\t$1();$N", [datInit]) + +proc genDatInitCode(m: BModule) = + ## this function is called in cgenWriteModules after all modules are closed, + ## it means raising dependency on the symbols is too late as it will not propogate + ## into other modules, only simple rope manipulations are allowed + + var moduleDatInitRequired = false + + var prc = "N_LIB_PRIVATE N_NIMCALL(void, $1)(void) {$N" % + [getDatInitName(m.module)] + + for i in cfsTypeInit1..cfsDynLibInit: + if m.s[i].len != 0: + moduleDatInitRequired = true + add(prc, genSectionStart(i, m.config)) + add(prc, m.s[i]) + add(prc, genSectionEnd(i, m.config)) + + addf(prc, "}$N$N", []) + + if moduleDatInitRequired: + add(m.s[cfsDatInitProc], prc) + proc genInitCode(m: BModule) = - var initname = getInitName(m.module) + ## this function is called in cgenWriteModules after all modules are closed, + ## it means raising dependency on the symbols is too late as it will not propogate + ## into other modules, only simple rope manipulations are allowed + + var moduleInitRequired = false + let initname = getInitName(m.module) var prc = "N_LIB_PRIVATE N_NIMCALL(void, $1)(void) {$N" % [initname] if m.typeNodes > 0: appcg(m, m.s[cfsTypeInit1], "static #TNimNode $1[$2];$n", @@ -1290,82 +1314,115 @@ proc genInitCode(m: BModule) = # Keep a bogus frame in case the code needs one add(prc, ~"\tTFrame FR_; FR_.len = 0;$N") + if m.preInitProc.s(cpsLocals).len > 0: + moduleInitRequired = true + add(prc, genSectionStart(cpsLocals, m.config)) + add(prc, m.preInitProc.s(cpsLocals)) + add(prc, genSectionEnd(cpsLocals, m.config)) + + if m.preInitProc.s(cpsInit).len > 0: + moduleInitRequired = true + add(prc, genSectionStart(cpsInit, m.config)) + add(prc, m.preInitProc.s(cpsInit)) + add(prc, genSectionEnd(cpsInit, m.config)) + + if m.preInitProc.s(cpsStmts).len > 0: + moduleInitRequired = true + add(prc, genSectionStart(cpsStmts, m.config)) + add(prc, m.preInitProc.s(cpsStmts)) + add(prc, genSectionEnd(cpsStmts, m.config)) + addf(prc, "}$N", []) + + if m.initProc.gcFrameId > 0: + moduleInitRequired = true + add(prc, initGCFrame(m.initProc)) + + if m.initProc.s(cpsLocals).len > 0: + moduleInitRequired = true add(prc, genSectionStart(cpsLocals, m.config)) - add(prc, m.preInitProc.s(cpsLocals)) + add(prc, m.initProc.s(cpsLocals)) add(prc, genSectionEnd(cpsLocals, m.config)) + if m.initProc.s(cpsInit).len > 0 or m.initProc.s(cpsStmts).len > 0: + moduleInitRequired = true + if optStackTrace in m.initProc.options and frameDeclared notin m.flags: + # BUT: the generated init code might depend on a current frame, so + # declare it nevertheless: + incl m.flags, frameDeclared + if preventStackTrace notin m.flags: + var procname = makeCString(m.module.name.s) + add(prc, initFrame(m.initProc, procname, quotedFilename(m.config, m.module.info))) + else: + add(prc, ~"\tTFrame FR_; FR_.len = 0;$N") + add(prc, genSectionStart(cpsInit, m.config)) - add(prc, m.preInitProc.s(cpsInit)) + add(prc, m.initProc.s(cpsInit)) add(prc, genSectionEnd(cpsInit, m.config)) add(prc, genSectionStart(cpsStmts, m.config)) - add(prc, m.preInitProc.s(cpsStmts)) + add(prc, m.initProc.s(cpsStmts)) add(prc, genSectionEnd(cpsStmts, m.config)) - addf(prc, "}$N", []) - - add(prc, initGCFrame(m.initProc)) - add(prc, genSectionStart(cpsLocals, m.config)) - add(prc, m.initProc.s(cpsLocals)) - add(prc, genSectionEnd(cpsLocals, m.config)) + if optStackTrace in m.initProc.options and preventStackTrace notin m.flags: + add(prc, deinitFrame(m.initProc)) - if optStackTrace in m.initProc.options and frameDeclared notin m.flags: - # BUT: the generated init code might depend on a current frame, so - # declare it nevertheless: - incl m.flags, frameDeclared - if preventStackTrace notin m.flags: - var procname = makeCString(m.module.name.s) - add(prc, initFrame(m.initProc, procname, quotedFilename(m.config, m.module.info))) - else: - add(prc, ~"\tTFrame FR_; FR_.len = 0;$N") - - add(prc, genSectionStart(cpsInit, m.config)) - add(prc, m.initProc.s(cpsInit)) - add(prc, genSectionEnd(cpsInit, m.config)) + if m.initProc.gcFrameId > 0: + moduleInitRequired = true + add(prc, deinitGCFrame(m.initProc)) - add(prc, genSectionStart(cpsStmts, m.config)) - add(prc, m.initProc.s(cpsStmts)) - add(prc, genSectionEnd(cpsStmts, m.config)) - - if optStackTrace in m.initProc.options and preventStackTrace notin m.flags: - add(prc, deinitFrame(m.initProc)) - add(prc, deinitGCFrame(m.initProc)) addf(prc, "}$N$N", []) - prc.addf("N_LIB_PRIVATE N_NIMCALL(void, $1)(void) {$N", - [getDatInitName(m.module)]) - - for i in cfsTypeInit1..cfsDynLibInit: - add(prc, genSectionStart(i, m.config)) - add(prc, m.s[i]) - add(prc, genSectionEnd(i, m.config)) - - addf(prc, "}$N$N", []) # we cannot simply add the init proc to ``m.s[cfsProcs]`` anymore because # that would lead to a *nesting* of merge sections which the merger does # not support. So we add it to another special section: ``cfsInitProc`` - add(m.s[cfsInitProc], prc) for i, el in pairs(m.extensionLoaders): if el != nil: let ex = "NIM_EXTERNC N_NIMCALL(void, nimLoadProcs$1)(void) {$2}$N$N" % [(i.ord - '0'.ord).rope, el] - add(m.s[cfsInitProc], ex) + moduleInitRequired = true + add(prc, ex) + + if moduleInitRequired or sfMainModule in m.module.flags: + add(m.s[cfsInitProc], prc) + + genDatInitCode(m) + registerModuleToMain(m.g, m) proc genModule(m: BModule, cfile: Cfile): Rope = + var moduleIsEmpty = true + result = getFileHeader(m.config, cfile) result.add(genMergeInfo(m)) + if m.config.cppCustomNamespace.len > 0: + result.add openNamespaceNim(m.config.cppCustomNamespace) + generateThreadLocalStorage(m) generateHeaders(m) - for i in countup(cfsHeaders, cfsProcs): - add(result, genSectionStart(i, m.config)) - add(result, m.s[i]) - add(result, genSectionEnd(i, m.config)) - if m.config.cppCustomNamespace.len > 0 and i == cfsHeaders: - result.add openNamespaceNim(m.config.cppCustomNamespace) - add(result, m.s[cfsInitProc]) - if m.config.cppCustomNamespace.len > 0: result.add closeNamespaceNim() + add(result, genSectionStart(cfsHeaders, m.config)) + add(result, m.s[cfsHeaders]) + add(result, genSectionEnd(cfsHeaders, m.config)) + + for i in countup(cfsForwardTypes, cfsProcs): + if m.s[i].len > 0: + moduleIsEmpty = false + add(result, genSectionStart(i, m.config)) + add(result, m.s[i]) + add(result, genSectionEnd(i, m.config)) + + if m.s[cfsInitProc].len > 0: + moduleIsEmpty = false + add(result, m.s[cfsInitProc]) + if m.s[cfsDatInitProc].len > 0: + moduleIsEmpty = false + add(result, m.s[cfsDatInitProc]) + + if m.config.cppCustomNamespace.len > 0: + result.add closeNamespaceNim() + + if moduleIsEmpty: + result = nil proc newPreInitProc(m: BModule): BProc = result = newProc(nil, m) @@ -1522,27 +1579,30 @@ proc writeModule(m: BModule, pending: bool) = finishTypeDescriptions(m) if sfMainModule in m.module.flags: # generate main file: + genMainProc(m) add(m.s[cfsProcHeaders], m.g.mainModProcs) generateThreadVarsSize(m) var cf = Cfile(cname: cfile, obj: completeCFilePath(m.config, toObjFile(m.config, cfile)), flags: {}) var code = genModule(m, cf) - when hasTinyCBackend: - if conf.cmd == cmdRun: - tccgen.compileCCode($code) - return - - if not shouldRecompile(m, code, cf): cf.flags = {CfileFlag.Cached} - addFileToCompile(m.config, cf) + if code != nil: + when hasTinyCBackend: + if conf.cmd == cmdRun: + tccgen.compileCCode($code) + return + + if not shouldRecompile(m, code, cf): cf.flags = {CfileFlag.Cached} + addFileToCompile(m.config, cf) elif pending and mergeRequired(m) and sfMainModule notin m.module.flags: let cf = Cfile(cname: cfile, obj: completeCFilePath(m.config, toObjFile(m.config, cfile)), flags: {}) mergeFiles(cfile, m) genInitCode(m) finishTypeDescriptions(m) var code = genModule(m, cf) - if not writeRope(code, cfile): - rawMessage(m.config, errCannotOpenFile, cfile.string) - addFileToCompile(m.config, cf) + if code != nil: + if not writeRope(code, cfile): + rawMessage(m.config, errCannotOpenFile, cfile.string) + addFileToCompile(m.config, cf) else: # Consider: first compilation compiles ``system.nim`` and produces # ``system.c`` but then compilation fails due to an error. This means @@ -1560,13 +1620,16 @@ proc updateCachedModule(m: BModule) = mergeFiles(cfile, m) genInitCode(m) finishTypeDescriptions(m) - var code = genModule(m, cf) - if not writeRope(code, cfile): - rawMessage(m.config, errCannotOpenFile, cfile.string) + if code != nil: + if not writeRope(code, cfile): + rawMessage(m.config, errCannotOpenFile, cfile.string) + addFileToCompile(m.config, cf) else: + if sfMainModule notin m.module.flags: + genMainProc(m) cf.flags = {CfileFlag.Cached} - addFileToCompile(m.config, cf) + addFileToCompile(m.config, cf) proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = result = n @@ -1579,15 +1642,25 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = if n != nil: m.initProc.options = initProcOptions(m) genStmts(m.initProc, n) - # cached modules need to registered too: - registerModuleToMain(m.g, m.module) if sfMainModule in m.module.flags: + # raise dependencies on behalf of genMainProc + if m.config.target.targetOS != osStandalone and m.config.selectedGC != gcNone: + discard cgsym(m, "initStackBottomWith") + if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone: + discard cgsym(m, "initThreadVarsEmulation") + + if m.g.breakpoints != nil: + discard cgsym(m, "dbgRegisterBreakpoint") + if optEndb in m.config.options: + discard cgsym(m, "dbgRegisterFilename") + if m.g.forwardedProcs.len == 0: incl m.flags, objHasKidsValid let disp = generateMethodDispatchers(graph) for x in disp: genProcAux(m, x.sym) - genMainProc(m) + + m.g.modules_closed.add m proc genForwardedProcs(g: BModuleList) = # Forward declared proc:s lack bodies when first encountered, so they're given diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim index 28e36364e..50b484e31 100644 --- a/compiler/cgendata.nim +++ b/compiler/cgendata.nim @@ -32,6 +32,7 @@ type cfsData, # section for C constant data cfsProcs, # section for C procs that are not inline cfsInitProc, # section for the C init proc + cfsDatInitProc, # section for the C datInit proc cfsTypeInit1, # section 1 for declarations of type information cfsTypeInit2, # section 2 for init of type information cfsTypeInit3, # section 3 for init of type information @@ -112,6 +113,7 @@ type mainModProcs*, mainModInit*, otherModsInit*, mainDatInit*: Rope mapping*: Rope # the generated mapping file (if requested) modules*: seq[BModule] # list of all compiled modules + modules_closed*: seq[BModule] # list of the same compiled modules, but in the order they were closed forwardedProcs*: seq[PSym] # proc:s that did not yet have a body generatedHeader*: BModule breakPointId*: int @@ -191,8 +193,6 @@ proc newModuleList*(g: ModuleGraph): BModuleList = graph: g, nimtvDeclared: initIntSet()) iterator cgenModules*(g: BModuleList): BModule = - for m in g.modules: - # ultimately, we are iterating over the file ids here. - # some "files" won't have an associated cgen module (like stdin) - # and we must skip over them. - if m != nil: yield m + for m in g.modules_closed: + # iterate modules in the order they were closed + yield m diff --git a/lib/system.nim b/lib/system.nim index bcd5fe05a..b9f86f549 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1479,12 +1479,9 @@ const # for string literals, it allows for some optimizations. {.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 +let nimvm* {.magic: "Nimvm", compileTime.}: bool = false + ## may be used only in "when" expression. + ## It is true in Nim VM context and false otherwise {.pop.} proc compileOption*(option: string): bool {. diff --git a/tests/ccgbugs/tforward_decl_only.nim b/tests/ccgbugs/tforward_decl_only.nim index 2a867bc3b..74fbae303 100644 --- a/tests/ccgbugs/tforward_decl_only.nim +++ b/tests/ccgbugs/tforward_decl_only.nim @@ -1,5 +1,7 @@ discard """ ccodecheck: "\\i !@('struct tyObject_MyRefObject'[0-z]+' {')" +ccodecheck: "\\i !@('mymoduleInit')" +ccodecheck: "\\i @('mymoduleDatInit')" output: "hello" """ diff --git a/tests/manyloc/standalone/barebone.nim b/tests/manyloc/standalone/barebone.nim index 9d75f8f2e..0b38616b2 100644 --- a/tests/manyloc/standalone/barebone.nim +++ b/tests/manyloc/standalone/barebone.nim @@ -1,4 +1,8 @@ - +discard """ +ccodecheck: "\\i !@('systemInit')" +ccodecheck: "\\i !@('systemDatInit')" +output: "hello" +""" # bug #2041: Macros need to be available for os:standalone! import macros -- cgit 1.4.1-2-gfad0 From 2ee2022c29ec4774eda51cb431052a9fc24982a7 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Sun, 30 Dec 2018 11:45:07 +0100 Subject: help Nim optimize intsets.initIntSet --- lib/pure/collections/intsets.nim | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/pure/collections/intsets.nim b/lib/pure/collections/intsets.nim index 8034e4c0a..f6d3a3d11 100644 --- a/lib/pure/collections/intsets.nim +++ b/lib/pure/collections/intsets.nim @@ -217,14 +217,13 @@ proc initIntSet*: IntSet = # newSeq(result.data, InitIntSetSize) # result.max = InitIntSetSize-1 - when defined(nimNoNilSeqs): - result.data = @[] - else: - result.data = nil - result.max = 0 - result.counter = 0 - result.head = nil - result.elems = 0 + result = IntSet( + elems: 0, + counter: 0, + max: 0, + head: nil, + data: when defined(nimNoNilSeqs): @[] else: nil) + # a: array[0..33, int] # profiling shows that 34 elements are enough proc clear*(result: var IntSet) = ## Clears the IntSet back to an empty state. -- cgit 1.4.1-2-gfad0 From bcbe317d177e9a22856e8dd6c0ad1e20436b913a Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Sun, 30 Dec 2018 23:04:41 +0530 Subject: Before showing deprecated warning, check whether enum field was marked deprecated or the whole enum type (#10135) --- compiler/suggest.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/suggest.nim b/compiler/suggest.nim index f149327ac..d3ce46172 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -464,7 +464,7 @@ proc extractPragma(s: PSym): PNode = proc warnAboutDeprecated(conf: ConfigRef; info: TLineInfo; s: PSym) = let pragmaNode = if s.kind == skEnumField: extractPragma(s.owner) else: extractPragma(s) let name = - if s.kind == skEnumField: "enum '" & s.owner.name.s & "' which contains field '" & s.name.s & "'" + if s.kind == skEnumField and sfDeprecated notin s.flags: "enum '" & s.owner.name.s & "' which contains field '" & s.name.s & "'" else: s.name.s if pragmaNode != nil: for it in pragmaNode: @@ -490,7 +490,7 @@ proc markUsed(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym) = if s.kind == skEnumField and s.owner != nil: incl(s.owner.flags, sfUsed) if sfDeprecated in s.owner.flags: - incl(s.flags, sfDeprecated) + warnAboutDeprecated(conf, info, s) if {sfDeprecated, sfError} * s.flags != {}: if sfDeprecated in s.flags: warnAboutDeprecated(conf, info, s) if sfError in s.flags: userError(conf, info, s) -- cgit 1.4.1-2-gfad0 From e87910197a209d02cf69bba3aacd9766723aeb37 Mon Sep 17 00:00:00 2001 From: deech Date: Mon, 31 Dec 2018 07:41:24 -0600 Subject: Check there are no side effects before optimizing away compile time expressions. (#9934) --- compiler/semexprs.nim | 20 ++++++++++------- compiler/semstmts.nim | 2 ++ tests/vm/tcompiletimesideeffects.nim | 42 ++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 tests/vm/tcompiletimesideeffects.nim diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index ddec457a1..45e259b65 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -710,16 +710,20 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode = let a = getConstExpr(c.module, n.sons[i], c.graph) if a == nil: return n call.add(a) + #echo "NOW evaluating at compile time: ", call.renderTree - if sfCompileTime in callee.flags: - result = evalStaticExpr(c.module, c.graph, call, c.p.owner) - if result.isNil: - localError(c.config, n.info, errCannotInterpretNodeX % renderTree(call)) - else: result = fixupTypeAfterEval(c, result, n) + if c.inStaticContext == 0 or sfNoSideEffect in callee.flags: + if sfCompileTime in callee.flags: + result = evalStaticExpr(c.module, c.graph, call, c.p.owner) + if result.isNil: + localError(c.config, n.info, errCannotInterpretNodeX % renderTree(call)) + else: result = fixupTypeAfterEval(c, result, n) + else: + result = evalConstExpr(c.module, c.graph, call) + if result.isNil: result = n + else: result = fixupTypeAfterEval(c, result, n) else: - result = evalConstExpr(c.module, c.graph, call) - if result.isNil: result = n - else: result = fixupTypeAfterEval(c, result, n) + result = n #if result != n: # echo "SUCCESS evaluated at compile time: ", call.renderTree diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 04991193c..f60e2556d 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -548,6 +548,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = proc semConst(c: PContext, n: PNode): PNode = result = copyNode(n) + inc c.inStaticContext for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] if c.config.cmd == cmdIdeTools: suggestStmt(c, a) @@ -607,6 +608,7 @@ proc semConst(c: PContext, n: PNode): PNode = v.ast = def[j] b.sons[j] = newSymNode(v) addSon(result,b) + dec c.inStaticContext include semfields diff --git a/tests/vm/tcompiletimesideeffects.nim b/tests/vm/tcompiletimesideeffects.nim new file mode 100644 index 000000000..4cd57b3bd --- /dev/null +++ b/tests/vm/tcompiletimesideeffects.nim @@ -0,0 +1,42 @@ +discard """ + output: +''' +@[0, 1, 2] +@[3, 4, 5] +@[0, 1, 2] +3 +4 +''' +""" + +template runNTimes(n: int, f : untyped) : untyped = + var accum: seq[type(f)] + for i in 0..n-1: + accum.add(f) + accum + +var state {.compileTime.} : int = 0 +proc fill(): int {.compileTime.} = + result = state + inc state + +# invoke fill() at compile time as a compile time expression +const C1 = runNTimes(3, fill()) +echo C1 + +# invoke fill() at compile time as a set of compile time statements +const C2 = + block: + runNTimes(3, fill()) +echo C2 + +# invoke fill() at compile time after a compile time reset of state +const C3 = + block: + state = 0 + runNTimes(3, fill()) +echo C3 + +# evaluate fill() at compile time and use the results at runtime +echo fill() +echo fill() -- cgit 1.4.1-2-gfad0 From 7ac1fc81fd119dba3739e05a89ad63fcee405ac1 Mon Sep 17 00:00:00 2001 From: c-blake Date: Mon, 31 Dec 2018 08:52:51 -0500 Subject: Resolve things raised in https://github.com/nim-lang/Nim/issues/10081 ? (#10084) * Resolve things raised in https://github.com/nim-lang/Nim/issues/10081 ? CDF is a standard ident in all things related to random numbers/sampling, and full words "cumulativeDistributionFunction" would be silly long, in this case, IMO. We use lowercase `cdf` to make it not look like a type, remove all looping from `sample` letting callers do it. Besides just side-stepping any `sampleSize` name choice, callers may want to filter out samples anyway which this makes slightly simpler. Also add two variants of `cumsum`, value return and in-place update distinguished by the var-ness of the first argument. Add tests for `int` and `float` for both `cumsum` and the new `sample`. (The sample tests exercise the value return mode of `cumsum`.) Functionality pre-this-PR `sample(a, w)` is now the almost as simple `for i in 0.. `cumsummed` to honor NEP1 style. Re-instate `cumsum` as the in-place transformation. Test both in `tests/stdlib/tmath.nim` and use `cumsummed` in the example code for sample since that's a simpler example. * Fix requests from https://github.com/nim-lang/Nim/pull/10084 : example in lib/pure/math.nim and comment whitespace in lib/pure/random.nim --- lib/pure/math.nim | 18 ++++++++++++++++ lib/pure/random.nim | 41 ++++++++++++++++++------------------ tests/stdlib/tmath.nim | 57 ++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 86 insertions(+), 30 deletions(-) diff --git a/lib/pure/math.nim b/lib/pure/math.nim index ee32772b1..72abae265 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -163,6 +163,24 @@ proc prod*[T](x: openArray[T]): T {.noSideEffect.} = result = 1.T for i in items(x): result = result * i +proc cumsummed*[T](x: openArray[T]): seq[T] = + ## Return cumulative aka prefix summation of ``x``. + ## + ## .. code-block:: nim + ## var x = [1, 2, 3, 4] + ## echo x.cumsummed # [1, 3, 6, 10] + result.setLen(x.len) + result[0] = x[0] + for i in 1 ..< x.len: result[i] = result[i-1] + x[i] + +proc cumsum*[T](x: var openArray[T]) = + ## Transforms ``x`` in-place into its cumulative aka prefix summation. + ## + ## .. code-block:: nim + ## var x = [1, 2, 3, 4] + ## x.cumsum; echo x # [1, 3, 6, 10] + for i in 1 ..< x.len: x[i] = x[i-1] + x[i] + {.push noSideEffect.} when not defined(JS): # C proc sqrt*(x: float32): float32 {.importc: "sqrtf", header: "".} diff --git a/lib/pure/random.nim b/lib/pure/random.nim index 26e6740ea..ee728ad4a 100644 --- a/lib/pure/random.nim +++ b/lib/pure/random.nim @@ -175,27 +175,26 @@ proc sample*[T](a: openArray[T]): T = ## returns a random element from openArray ``a`` using non-thread-safe state. result = a[rand(a.low..a.high)] -proc sample*[T, U](r: var Rand; a: openArray[T], w: openArray[U], n=1): seq[T] = - ## Return a sample (with replacement) of size ``n`` from elements of ``a`` - ## according to convertible-to-``float``, not necessarily normalized, and - ## non-negative weights ``w``. Uses state in ``r``. Must have sum ``w > 0.0``. - assert(w.len == a.len) - var cdf = newSeq[float](a.len) # The *unnormalized* CDF - var tot = 0.0 # Unnormalized is fine if we sample up to tot - for i, w in w: - assert(w >= 0) - tot += float(w) - cdf[i] = tot - assert(tot > 0.0) # Need at least one non-zero weight - for i in 0 ..< n: - result.add(a[cdf.upperBound(r.rand(tot))]) - -proc sample*[T, U](a: openArray[T], w: openArray[U], n=1): seq[T] = - ## Return a sample (with replacement) of size ``n`` from elements of ``a`` - ## according to convertible-to-``float``, not necessarily normalized, and - ## non-negative weights ``w``. Uses default non-thread-safe state. - state.sample(a, w, n) - +proc sample*[T, U](r: var Rand; a: openArray[T], cdf: openArray[U]): T = + ## Sample one element from openArray ``a`` when it has cumulative distribution + ## function (CDF) ``cdf`` (not necessarily normalized, any type of elements + ## convertible to ``float``). Uses state in ``r``. E.g.: + ## + ## .. code-block:: nim + ## let val = [ "a", "b", "c", "d" ] # some values + ## var cnt = [1, 2, 3, 4] # histogram of counts + ## echo r.sample(val, cnt.cumsummed) # echo a sample + assert(cdf.len == a.len) # Two basic sanity checks. + assert(float(cdf[^1]) > 0.0) + #While we could check cdf[i-1] <= cdf[i] for i in 1..cdf.len, that could get + #awfully expensive even in debugging modes. + let u = r.rand(float(cdf[^1])) + a[cdf.upperBound(U(u))] + +proc sample*[T, U](a: openArray[T], cdf: openArray[U]): T = + ## Like ``sample(var Rand; openArray[T], openArray[U])``, but uses default + ## non-thread-safe state. + state.sample(a, cdf) proc initRand*(seed: int64): Rand = ## Creates a new ``Rand`` state from ``seed``. diff --git a/tests/stdlib/tmath.nim b/tests/stdlib/tmath.nim index 7c1851e7a..544fa8dc0 100644 --- a/tests/stdlib/tmath.nim +++ b/tests/stdlib/tmath.nim @@ -4,6 +4,8 @@ discard """ [Suite] random float +[Suite] cumsum + [Suite] random sample [Suite] ^ @@ -74,29 +76,66 @@ suite "random float": var rand2:float = random(1000000.0) check rand1 != rand2 +suite "cumsum": + test "cumsum int seq return": + let counts = [ 1, 2, 3, 4 ] + check counts.cumsummed == [ 1, 3, 6, 10 ] + + test "cumsum float seq return": + let counts = [ 1.0, 2.0, 3.0, 4.0 ] + check counts.cumsummed == [ 1.0, 3.0, 6.0, 10.0 ] + + test "cumsum int in-place": + var counts = [ 1, 2, 3, 4 ] + counts.cumsum + check counts == [ 1, 3, 6, 10 ] + + test "cumsum float in-place": + var counts = [ 1.0, 2.0, 3.0, 4.0 ] + counts.cumsum + check counts == [ 1.0, 3.0, 6.0, 10.0 ] + suite "random sample": - test "non-uniform array sample": + test "non-uniform array sample unnormalized int CDF": let values = [ 10, 20, 30, 40, 50 ] # values - let weight = [ 4, 3, 2, 1, 0 ] # weights aka unnormalized probabilities - let weightSum = 10.0 # sum of weights + let counts = [ 4, 3, 2, 1, 0 ] # weights aka unnormalized probabilities var histo = initCountTable[int]() - for v in sample(values, weight, 5000): - histo.inc(v) - check histo.len == 4 # number of non-zero in `weight` + let cdf = counts.cumsummed # unnormalized CDF + for i in 0 ..< 5000: + histo.inc(sample(values, cdf)) + check histo.len == 4 # number of non-zero in `counts` # Any one bin is a binomial random var for n samples, each with prob p of # adding a count to k; E[k]=p*n, Var k=p*(1-p)*n, approximately Normal for # big n. So, P(abs(k - p*n)/sqrt(p*(1-p)*n))>3.0) =~ 0.0027, while # P(wholeTestFails) =~ 1 - P(binPasses)^4 =~ 1 - (1-0.0027)^4 =~ 0.01. - for i, w in weight: - if w == 0: + for i, c in counts: + if c == 0: check values[i] notin histo continue - let p = float(w) / float(weightSum) + let p = float(c) / float(cdf[^1]) let n = 5000.0 let expected = p * n let stdDev = sqrt(n * p * (1.0 - p)) check abs(float(histo[values[i]]) - expected) <= 3.0 * stdDev + test "non-uniform array sample normalized float CDF": + let values = [ 10, 20, 30, 40, 50 ] # values + let counts = [ 0.4, 0.3, 0.2, 0.1, 0 ] # probabilities + var histo = initCountTable[int]() + let cdf = counts.cumsummed # normalized CDF + for i in 0 ..< 5000: + histo.inc(sample(values, cdf)) + check histo.len == 4 # number of non-zero in ``counts`` + for i, c in counts: + if c == 0: + check values[i] notin histo + continue + let p = float(c) / float(cdf[^1]) + let n = 5000.0 + let expected = p * n + let stdDev = sqrt(n * p * (1.0 - p)) + # NOTE: like unnormalized int CDF test, P(wholeTestFails) =~ 0.01. + check abs(float(histo[values[i]]) - expected) <= 3.0 * stdDev suite "^": test "compiles for valid types": -- cgit 1.4.1-2-gfad0 From ab72d68ec80300ebd0629c5174a78c73f28fc729 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Mon, 31 Dec 2018 06:42:01 -0800 Subject: fix off by 1 error in `col` shown by toFileLineCol (#10138) * fix off by 1 error in `col` shown by toFileLineCol * fix test failures --- compiler/msgs.nim | 2 +- tests/discard/tneedsdiscard.nim | 2 +- tests/method/tmapper.nim | 2 +- tests/modules/tmismatchedvisibility.nim | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 7e6b67cbe..18c196085 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -208,7 +208,7 @@ proc toFileLine*(conf: ConfigRef; info: TLineInfo): string {.inline.} = result = toFilename(conf, info) & ":" & $info.line proc toFileLineCol*(conf: ConfigRef; info: TLineInfo): string {.inline.} = - result = toFilename(conf, info) & "(" & $info.line & ", " & $info.col & ")" + result = toFilename(conf, info) & "(" & $info.line & ", " & $(info.col+1) & ")" proc `$`*(conf: ConfigRef; info: TLineInfo): string = toFileLineCol(conf, info) diff --git a/tests/discard/tneedsdiscard.nim b/tests/discard/tneedsdiscard.nim index 7d2997b3f..d9483947f 100644 --- a/tests/discard/tneedsdiscard.nim +++ b/tests/discard/tneedsdiscard.nim @@ -1,5 +1,5 @@ discard """ - errormsg: '''expression 'open(f, "arg.txt", fmRead, -1)' is of type 'bool' and has to be discarded; start of expression here: tneedsdiscard.nim(7, 2)''' + errormsg: '''expression 'open(f, "arg.txt", fmRead, -1)' is of type 'bool' and has to be discarded; start of expression here: tneedsdiscard.nim(7, 3)''' line: 10 """ diff --git a/tests/method/tmapper.nim b/tests/method/tmapper.nim index a5d03f700..9162d0eec 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: tmapper.nim(22, 7)" + errormsg: "invalid declaration order; cannot attach 'step' to method defined here: tmapper.nim(22, 8)" line: 25 """ diff --git a/tests/modules/tmismatchedvisibility.nim b/tests/modules/tmismatchedvisibility.nim index 4bf244807..fd582b571 100644 --- a/tests/modules/tmismatchedvisibility.nim +++ b/tests/modules/tmismatchedvisibility.nim @@ -1,5 +1,5 @@ discard """ - errormsg: "public implementation 'tmismatchedvisibility.foo(a: int)[declared in tmismatchedvisibility.nim(6, 5)]' has non-public forward declaration in " + errormsg: "public implementation 'tmismatchedvisibility.foo(a: int)[declared in tmismatchedvisibility.nim(6, 6)]' has non-public forward declaration in " line: 8 """ -- cgit 1.4.1-2-gfad0 From 7c90e22ddd354692d259c5620578f194757c20e1 Mon Sep 17 00:00:00 2001 From: cooldome Date: Mon, 31 Dec 2018 21:57:09 +0000 Subject: fixes #10148 (#10149) * fixes #10148 * fix a typo --- compiler/cgen.nim | 31 ++++++++++++++++++------------- tests/cpp/t10148.nim | 29 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 tests/cpp/t10148.nim diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 3d76be254..e4f16f4ed 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -1103,9 +1103,8 @@ proc genMainProc(m: BModule) = "}$N$N" & "void PreMain(void) {$N" & "\tvoid (*volatile inner)(void);$N" & - "$1" & "\tinner = PreMainInner;$N" & - "$4$5" & + "$1" & "\t(*inner)();$N" & "}$N$N" @@ -1222,12 +1221,7 @@ proc genMainProc(m: BModule) = else: ropecg(m, "\t#initStackBottomWith((void *)&inner);$N") inc(m.labels) appcg(m, m.s[cfsProcs], PreMainBody, [ - m.g.mainDatInit, m.g.breakpoints, m.g.otherModsInit, - if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone: - ropecg(m, "\t#initThreadVarsEmulation();$N") - else: - "".rope, - initStackBottomCall]) + m.g.mainDatInit, m.g.breakpoints, m.g.otherModsInit]) appcg(m, m.s[cfsProcs], nimMain, [m.g.mainModInit, initStackBottomCall, rope(m.labels)]) @@ -1256,21 +1250,32 @@ proc getInitName(m: PSym): Rope = proc getDatInitName(m: PSym): Rope = getSomeInitName(m, "DatInit000") + proc registerModuleToMain(g: BModuleList; m: BModule) = + if m.s[cfsDatInitProc].len > 0: + let datInit = m.module.getDatInitName + addf(g.mainModProcs, "N_LIB_PRIVATE N_NIMCALL(void, $1)(void);$N", [datInit]) + addf(g.mainDatInit, "\t$1();$N", [datInit]) + + # Initialization of TLS and GC should be done in between + # systemDatInit and systemInit calls if any + if sfSystemModule in m.module.flags: + if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone: + add(g.mainDatInit, ropecg(m, "\t#initThreadVarsEmulation();$N")) + if m.config.target.targetOS != osStandalone and m.config.selectedGC != gcNone: + add(g.mainDatInit, ropecg(m, "\t#initStackBottomWith((void *)&inner);$N")) + if m.s[cfsInitProc].len > 0: let init = m.module.getInitName addf(g.mainModProcs, "N_LIB_PRIVATE N_NIMCALL(void, $1)(void);$N", [init]) let initCall = "\t$1();$N" % [init] if sfMainModule in m.module.flags: add(g.mainModInit, initCall) + elif sfSystemModule in m.module.flags: + add(g.mainDatInit, initCall) # systemInit must called right after systemDatInit if any else: add(g.otherModsInit, initCall) - if m.s[cfsDatInitProc].len > 0: - let datInit = m.module.getDatInitName - addf(g.mainModProcs, "N_LIB_PRIVATE N_NIMCALL(void, $1)(void);$N", [datInit]) - addf(g.mainDatInit, "\t$1();$N", [datInit]) - proc genDatInitCode(m: BModule) = ## this function is called in cgenWriteModules after all modules are closed, ## it means raising dependency on the symbols is too late as it will not propogate diff --git a/tests/cpp/t10148.nim b/tests/cpp/t10148.nim new file mode 100644 index 000000000..e8dd3098f --- /dev/null +++ b/tests/cpp/t10148.nim @@ -0,0 +1,29 @@ +discard """ + output: '''Expected successful exit''' + joinable: false +""" + +import os + +proc another_proc: string = + ## trigger many GC allocations + var x = @[""] + for i in 0..100: + x.add $i + result = "not_existent_path" + +proc findlib2: string = + let path = getEnv("MYLIB2_DOES_NOT_EXIST_PATH") + let another_path = another_proc() + GC_fullCollect() + + if path.len > 0 and dirExists(path): + path / "alib_does_not_matter.dll" + elif fileExists(another_path): + another_path + else: + quit("Expected successful exit", 0) + +proc imported_func*(a: cint): cstring {.importc, dynlib: findlib2().} + +echo imported_func(0) -- cgit 1.4.1-2-gfad0 From e7fa8f3443ebe03d74fb20b7f4d0096756211463 Mon Sep 17 00:00:00 2001 From: jiro Date: Tue, 1 Jan 2019 17:56:59 +0900 Subject: Add Example code to Documentation comment of wordWrap proc. (#10146) --- lib/std/wordwrap.nim | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/std/wordwrap.nim b/lib/std/wordwrap.nim index c7898b339..4b0dc4417 100644 --- a/lib/std/wordwrap.nim +++ b/lib/std/wordwrap.nim @@ -24,6 +24,11 @@ proc wrapWords*(s: string, maxLineWidth = 80, seps: set[char] = Whitespace, newLine = "\n"): string {.noSideEffect.} = ## Word wraps `s`. + runnableExamples: + doAssert "12345678901234567890".wrapWords() == "12345678901234567890" + doAssert "123456789012345678901234567890".wrapWords(20) == "12345678901234567890\n1234567890" + doAssert "Hello Bob. Hello John.".wrapWords(13, false) == "Hello Bob.\nHello John." + doAssert "Hello Bob. Hello John.".wrapWords(13, true, {';'}) == "Hello Bob. He\nllo John." result = newStringOfCap(s.len + s.len shr 6) var spaceLeft = maxLineWidth var lastSep = "" -- cgit 1.4.1-2-gfad0 From 9faad7591e37fa074544206d1de17e59f56bd576 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Tue, 1 Jan 2019 18:20:48 +0530 Subject: Deprecate gc v2 (#10151) * Deprecate gc v2 * warnDeprecated now has custom messages --- compiler/commands.nim | 4 ++-- compiler/importer.nim | 4 ++-- compiler/lineinfos.nim | 2 +- compiler/lookups.nim | 2 +- compiler/modulepaths.nim | 2 +- compiler/parser.nim | 4 ++-- compiler/pragmas.nim | 12 ++++++------ compiler/semexprs.nim | 2 +- compiler/semstmts.nim | 2 +- compiler/semtypes.nim | 2 +- compiler/suggest.nim | 4 ++-- doc/advopt.txt | 2 +- 12 files changed, 21 insertions(+), 21 deletions(-) diff --git a/compiler/commands.nim b/compiler/commands.nim index 5893791cc..8b73884e8 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -224,7 +224,7 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo case arg.normalize of "boehm": result = conf.selectedGC == gcBoehm of "refc": result = conf.selectedGC == gcRefc - of "v2": result = conf.selectedGC == gcV2 + of "v2": result = false of "markandsweep": result = conf.selectedGC == gcMarkAndSweep of "generational": result = false of "destructors": result = conf.selectedGC == gcDestructors @@ -442,7 +442,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; of "refc": conf.selectedGC = gcRefc of "v2": - conf.selectedGC = gcV2 + message(conf, info, warnDeprecated, "--gc:v2 is deprecated; using default gc") of "markandsweep": conf.selectedGC = gcMarkAndSweep defineSymbol(conf.symbols, "gcmarkandsweep") diff --git a/compiler/importer.nim b/compiler/importer.nim index 118d26d80..eef0c9bb9 100644 --- a/compiler/importer.nim +++ b/compiler/importer.nim @@ -162,9 +162,9 @@ proc myImportModule(c: PContext, n: PNode; importStmtResult: PNode): PSym = localError(c.config, n.info, "A module cannot import itself") if sfDeprecated in result.flags: if result.constraint != nil: - message(c.config, n.info, warnDeprecated, result.constraint.strVal & "; " & result.name.s) + message(c.config, n.info, warnDeprecated, result.constraint.strVal & "; " & result.name.s & " is deprecated") else: - message(c.config, n.info, warnDeprecated, result.name.s) + message(c.config, n.info, warnDeprecated, result.name.s & " is deprecated") suggestSym(c.config, n.info, result, c.graph.usageSym, false) importStmtResult.add newSymNode(result, n.info) #newStrNode(toFullPath(c.config, f), n.info) diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index b1ecf779e..21ce44406 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -65,7 +65,7 @@ const warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored", warnXIsNeverRead: "'$1' is never read", warnXmightNotBeenInit: "'$1' might not have been initialized", - warnDeprecated: "$1 is deprecated", + warnDeprecated: "$1", warnConfigDeprecated: "config file '$1' is deprecated", warnSmallLshouldNotBeUsed: "'l' should not be used as an identifier; may look like '1' (one)", warnUnknownMagic: "unknown magic '$1' might crash the compiler", diff --git a/compiler/lookups.nim b/compiler/lookups.nim index 11a741505..d4959db12 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -89,7 +89,7 @@ proc skipAlias*(s: PSym; n: PNode; conf: ConfigRef): PSym = prettybase.replaceDeprecated(conf, n.info, s, result) else: message(conf, n.info, warnDeprecated, "use " & result.name.s & " instead; " & - s.name.s) + s.name.s & " is deprecated") proc localSearchInScope*(c: PContext, s: PIdent): PSym = result = strTableGet(c.currentScope.symbols, s) diff --git a/compiler/modulepaths.nim b/compiler/modulepaths.nim index f0718c4eb..9e27a2d7d 100644 --- a/compiler/modulepaths.nim +++ b/compiler/modulepaths.nim @@ -147,7 +147,7 @@ proc getModuleName*(conf: ConfigRef; n: PNode): string = # hacky way to implement 'x / y /../ z': result = renderTree(n, {renderNoComments}).replace(" ") of nkDotExpr: - localError(conf, n.info, warnDeprecated, "using '.' instead of '/' in import paths") + localError(conf, n.info, warnDeprecated, "using '.' instead of '/' in import paths is deprecated") result = renderTree(n, {renderNoComments}).replace(".", "/") of nkImportAs: result = getModuleName(conf, n.sons[0]) diff --git a/compiler/parser.nim b/compiler/parser.nim index 163ad6455..afc49c0d3 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -1916,7 +1916,7 @@ proc parseObject(p: var TParser): PNode = getTok(p) if p.tok.tokType == tkCurlyDotLe and p.validInd: # Deprecated since v0.20.0 - parMessage(p, warnDeprecated, "type pragmas follow the type name; this form of writing pragmas") + parMessage(p, warnDeprecated, "type pragmas follow the type name; this form of writing pragmas is deprecated") addSon(result, parsePragma(p)) else: addSon(result, p.emptyNode) @@ -2008,7 +2008,7 @@ proc parseTypeDef(p: var TParser): PNode = if p.tok.tokType == tkBracketLe and p.validInd: if not noPragmaYet: # Deprecated since v0.20.0 - parMessage(p, warnDeprecated, "pragma before generic parameter list") + parMessage(p, warnDeprecated, "pragma before generic parameter list is deprecated") genericParam = parseGenericParamList(p) else: genericParam = p.emptyNode diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 58f64f7b0..39b58d0b1 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -242,7 +242,7 @@ proc pragmaNoForward(c: PContext, n: PNode; flag=sfNoForward) = # deprecated as of 0.18.1 message(c.config, n.info, warnDeprecated, "use {.experimental: \"codeReordering.\".} instead; " & - (if flag == sfNoForward: "{.noForward.}" else: "{.reorder.}")) + (if flag == sfNoForward: "{.noForward.}" else: "{.reorder.}") & " is deprecated") proc processCallConv(c: PContext, n: PNode) = if n.kind in nkPragmaCallKinds and n.len == 2 and n.sons[1].kind == nkIdent: @@ -447,14 +447,14 @@ proc processPop(c: PContext, n: PNode) = proc processDefine(c: PContext, n: PNode) = if (n.kind in nkPragmaCallKinds and n.len == 2) and (n[1].kind == nkIdent): defineSymbol(c.config.symbols, n[1].ident.s) - message(c.config, n.info, warnDeprecated, "define") + message(c.config, n.info, warnDeprecated, "define is deprecated") else: invalidPragma(c, n) proc processUndef(c: PContext, n: PNode) = if (n.kind in nkPragmaCallKinds and n.len == 2) and (n[1].kind == nkIdent): undefSymbol(c.config.symbols, n[1].ident.s) - message(c.config, n.info, warnDeprecated, "undef") + message(c.config, n.info, warnDeprecated, "undef is deprecated") else: invalidPragma(c, n) @@ -784,7 +784,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, if sym.kind in {skTemplate, skMacro}: incl(sym.flags, sfImmediate) incl(sym.flags, sfAllUntyped) - message(c.config, n.info, warnDeprecated, "use 'untyped' parameters instead; immediate") + message(c.config, n.info, warnDeprecated, "use 'untyped' parameters instead; immediate is deprecated") else: invalidPragma(c, it) of wDirty: if sym.kind == skTemplate: incl(sym.flags, sfDirty) @@ -1098,10 +1098,10 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, of wThis: if it.kind in nkPragmaCallKinds and it.len == 2: c.selfName = considerQuotedIdent(c, it[1]) - message(c.config, n.info, warnDeprecated, "the '.this' pragma") + message(c.config, n.info, warnDeprecated, "the '.this' pragma is deprecated") elif it.kind == nkIdent or it.len == 1: c.selfName = getIdent(c.cache, "self") - message(c.config, n.info, warnDeprecated, "the '.this' pragma") + message(c.config, n.info, warnDeprecated, "the '.this' pragma is deprecated") else: localError(c.config, it.info, "'this' pragma is allowed to have zero or one arguments") of wNoRewrite: diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 45e259b65..19d008557 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -2434,7 +2434,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = result.kind = nkCall result = semExpr(c, result, flags) of nkBind: - message(c.config, n.info, warnDeprecated, "bind") + message(c.config, n.info, warnDeprecated, "bind is deprecated") result = semExpr(c, n.sons[0], flags) of nkTypeOfExpr, nkTupleTy, nkTupleClassTy, nkRefTy..nkEnumTy, nkStaticTy: if c.matchedConcept != nil and n.len == 1: diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index f60e2556d..59eb23162 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1582,7 +1582,7 @@ proc semMethodPrototype(c: PContext; s: PSym; n: PNode) = foundObj = true x.methods.add((col,s)) if not foundObj: - message(c.config, n.info, warnDeprecated, "generic method not attachable to object type") + message(c.config, n.info, warnDeprecated, "generic method not attachable to object type is deprecated") else: # why check for the body? bug #2400 has none. Checking for sfForward makes # no sense either. diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 200b247ca..d1ea54eca 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -210,7 +210,7 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType = tyError, tyObject}: message c.config, n[i].info, errGenerated, "region needs to be an object type" else: - message(c.config, n.info, warnDeprecated, "region for pointer types") + message(c.config, n.info, warnDeprecated, "region for pointer types is deprecated") addSonSkipIntLit(result, region) addSonSkipIntLit(result, t) if tfPartial in result.flags: diff --git a/compiler/suggest.nim b/compiler/suggest.nim index d3ce46172..144b86224 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -470,9 +470,9 @@ proc warnAboutDeprecated(conf: ConfigRef; info: TLineInfo; s: PSym) = for it in pragmaNode: if whichPragma(it) == wDeprecated and it.safeLen == 2 and it[1].kind in {nkStrLit..nkTripleStrLit}: - message(conf, info, warnDeprecated, it[1].strVal & "; " & name) + message(conf, info, warnDeprecated, it[1].strVal & "; " & name & " is deprecated") return - message(conf, info, warnDeprecated, name) + message(conf, info, warnDeprecated, name & " is deprecated") proc userError(conf: ConfigRef; info: TLineInfo; s: PSym) = let pragmaNode = extractPragma(s) diff --git a/doc/advopt.txt b/doc/advopt.txt index 7445068c1..75260baad 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -82,7 +82,7 @@ Advanced options: --skipUserCfg do not read the user's configuration file --skipParentCfg do not read the parent dirs' configuration files --skipProjCfg do not read the project's configuration file - --gc:refc|v2|markAndSweep|boehm|go|none|regions + --gc:refc|markAndSweep|boehm|go|none|regions select the GC to use; default is 'refc' --index:on|off turn index file generation on|off --putenv:key=value set an environment variable -- cgit 1.4.1-2-gfad0 From 6ed9676af5ae17fa15551a00f1ad5f3be109233f Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Tue, 1 Jan 2019 23:19:44 +0530 Subject: Show field not initialized warning only when `notnil` used (#10155) --- compiler/semtypes.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index d1ea54eca..f4936a71a 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -141,7 +141,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType = if isPure and (let conflict = strTableInclReportConflict(symbols, e); conflict != nil): wrongRedefinition(c, e.info, e.name.s, conflict.info) inc(counter) - if not hasNull: incl(result.flags, tfNeedsInit) + if tfNotNil in e.typ.flags and not hasNull: incl(result.flags, tfNeedsInit) proc semSet(c: PContext, n: PNode, prev: PType): PType = result = newOrPrevType(tySet, prev, c) -- cgit 1.4.1-2-gfad0 From 31b8bc7866e8b024daeb0be153144e3398933559 Mon Sep 17 00:00:00 2001 From: Ico Doornekamp Date: Tue, 1 Jan 2019 19:49:29 +0100 Subject: Add link to tutorial part III to docs.rst (#10157) [ci skip] --- doc/docs.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/docs.rst b/doc/docs.rst index cd1a05853..4a69bd69a 100644 --- a/doc/docs.rst +++ b/doc/docs.rst @@ -6,6 +6,9 @@ The documentation consists of several documents: - | `Tutorial (part II) `_ | The Nim tutorial part two deals with the advanced language constructs. +- | `Tutorial (part III) `_ + | The Nim tutorial part three about Nim's macro system. + - | `Language Manual `_ | The Nim manual is a draft that will evolve into a proper specification. -- cgit 1.4.1-2-gfad0 From a1e268e3dccdde4df9b11a0ee87971e1143fbb43 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 2 Jan 2019 01:26:40 -0800 Subject: [cleanup] remove dead code compiler.options.mergeConfigs (#10165) --- compiler/options.nim | 78 ---------------------------------------------------- 1 file changed, 78 deletions(-) diff --git a/compiler/options.nim b/compiler/options.nim index 49d2f7404..448741ddb 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -253,84 +253,6 @@ template depConfigFields*(fn) {.dirty.} = fn(globalOptions) fn(selectedGC) -proc mergeConfigs*(dest, src: ConfigRef; mergeSymbols: bool) = - template merge[T: enum](a, b: T) = - a = b - template merge[T](a, b: set[T]) = - a = a + b - template merge(a, b: int) = - inc a, b - template merge[T](a, b: seq[T]) = - for bb in b: a.add b - template merge(a, b: string) = - a = b - template merge[T: AbsoluteFile|AbsoluteDir](a, b: T) = - if a.isEmpty and not b.isEmpty: a = b - - template merge[T](a, b: HashSet[T]) = - for bb in b: a.incl b - template merge(a, b: StringTableRef) = - for k, v in b: a[k] = v - template merge[T: object](a, b: T) = - a = b - - template m(field) = - merge(dest.field, src.field) - - m target - m options - m globalOptions - m cmd - m selectedGC - dest.verbosity = src.verbosity - m numberOfProcessors - m evalExpr - m symbolFiles - m cppDefines - m headerFile - m features - m arguments - m ideCmd - m cCompiler - m enableNotes - m disableNotes - m foreignPackageNotes - m notes - m errorCounter - m hintCounter - m warnCounter - m errorMax - m configVars - if mergeSymbols: - m symbols - m projectName - m projectPath - m projectFull - m searchPaths - m lazyPaths - m outFile - m prefixDir - m libpath - m nimcacheDir - m dllOverrides - m moduleOverrides - m command - m commandArgs - m implicitImports - m implicitIncludes - m docSeeSrcUrl - m cIncludes - m cLibs - m cLinkedLibs - m externalToLink - m linkOptionsCmd - m compileOptionsCmd - m linkOptions - m compileOptions - m ccompilerpath - m toCompile - m cppCustomNamespace - const oldExperimentalFeatures* = {implicitDeref, dotOperators, callOperator, parallel} const -- cgit 1.4.1-2-gfad0 From 9fb8c3d965ebf760c0636838eec1217ee7be7caa Mon Sep 17 00:00:00 2001 From: Oscar Nihlgård Date: Wed, 2 Jan 2019 17:39:53 +0100 Subject: Add {.noReturn.} to system.raiseAssert (#10161) --- lib/system.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/system.nim b/lib/system.nim index b9f86f549..cab8a91aa 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -3927,7 +3927,7 @@ proc instantiationInfo*(index = -1, fullPaths = false): tuple[ template currentSourcePath*: string = instantiationInfo(-1, true).filename ## returns the full file-system path of the current source -proc raiseAssert*(msg: string) {.noinline.} = +proc raiseAssert*(msg: string) {.noinline, noReturn.} = sysFatal(AssertionError, msg) proc failedAssertImpl*(msg: string) {.raises: [], tags: [].} = -- cgit 1.4.1-2-gfad0 From 7c5ae008874e7bb76214bca96267bcb400a723d1 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Thu, 3 Jan 2019 00:31:06 +0530 Subject: exportc is now not allowed for type aliases (#9979) --- compiler/semstmts.nim | 2 ++ tests/pragmas/t5149.nim | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 tests/pragmas/t5149.nim diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 59eb23162..39f200ba8 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1101,6 +1101,8 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) = #debug s.typ s.ast = a popOwner(c) + if sfExportc in s.flags and s.typ.kind == tyAlias: + localError(c.config, name.info, "{.exportc.} not allowed for type aliases") let aa = a.sons[2] if aa.kind in {nkRefTy, nkPtrTy} and aa.len == 1 and aa.sons[0].kind == nkObjectTy: diff --git a/tests/pragmas/t5149.nim b/tests/pragmas/t5149.nim new file mode 100644 index 000000000..2d242a8d5 --- /dev/null +++ b/tests/pragmas/t5149.nim @@ -0,0 +1,12 @@ +discard """ + errormsg: "{.exportc.} not allowed for type aliases" + line: 9 +""" + +type + X* = object + a: int + Y* {.exportc.} = X + +proc impl*(x: X) = + echo "it works" -- cgit 1.4.1-2-gfad0 From e6cbf9e79014e2fb6851796adda251eed1abcfd5 Mon Sep 17 00:00:00 2001 From: Araq Date: Thu, 3 Jan 2019 23:45:42 +0100 Subject: koch.nim: speed up tests for the 'testinstall' target for the nightly builds (these tests are also covered by the other CIs) --- koch.nim | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/koch.nim b/koch.nim index 9596ce21f..3082dba8f 100644 --- a/koch.nim +++ b/koch.nim @@ -475,8 +475,7 @@ proc testUnixInstall(cmdLineRest: string) = execCleanPath("./koch --latest tools") # check the tests work: putEnv("NIM_EXE_NOT_IN_PATH", "NOT_IN_PATH") - execCleanPath("./koch tests", destDir / "bin") - #execCleanPath("./koch tests cat newconfig", destDir / "bin") + execCleanPath("./koch tests cat megatest", destDir / "bin") else: echo "Version check: failure" finally: -- cgit 1.4.1-2-gfad0 From 77166ba795b03d4536f8efb0f225ba3ffc8531be Mon Sep 17 00:00:00 2001 From: Miran Date: Fri, 4 Jan 2019 09:25:58 +0100 Subject: [backport] correctly document `toInt`, fixes #2764 [ci skip] (#10176) --- lib/system.nim | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/system.nim b/lib/system.nim index cab8a91aa..9fa889b11 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1799,14 +1799,16 @@ proc toBiggestFloat*(i: BiggestInt): BiggestFloat {. proc toInt*(f: float): int {. magic: "ToInt", noSideEffect, importc: "toInt".} ## converts a floating point number `f` into an ``int``. Conversion - ## rounds `f` if it does not contain an integer value. If the conversion - ## fails (because `f` is infinite for example), `ValueError` is raised. + ## rounds `f` if it does not contain an integer value. + ## Note that some floating point numbers (e.g. infinity) cannot be + ## accurately converted. proc toBiggestInt*(f: BiggestFloat): BiggestInt {. magic: "ToBiggestInt", noSideEffect, importc: "toBiggestInt".} ## converts a biggestfloat `f` into a ``biggestint``. Conversion - ## rounds `f` if it does not contain an integer value. If the conversion - ## fails (because `f` is infinite for example), `ValueError` is raised. + ## rounds `f` if it does not contain an integer value. + ## Note that some floating point numbers (e.g. infinity) cannot be + ## accurately converted. proc addQuitProc*(quitProc: proc() {.noconv.}) {. importc: "atexit", header: "".} -- cgit 1.4.1-2-gfad0 From 086229459aa72f06d31a4f920653a82a74cfc6b0 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 4 Jan 2019 09:52:01 +0100 Subject: runnableExamples: compile and test them as a single file; fixes #9300 --- compiler/docgen.nim | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 67f4108e1..0fe19c5bc 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -40,6 +40,7 @@ type # already. See bug #3655 destFile*: AbsoluteFile thisDir*: AbsoluteDir + examples: string PDoc* = ref TDocumentor ## Alias to type less. @@ -378,6 +379,13 @@ proc testExample(d: PDoc; ex: PNode) = "_examples" & $d.exampleCounter & ".nim")) #let nimcache = outp.changeFileExt"" & "_nimcache" renderModule(ex, d.filename, outp.string, conf = d.conf) + d.examples.add "import r\"" & outp.string & "\"\n" + +proc runAllExamples(d: PDoc) = + let outputDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples" + let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" & + "_examples.nim")) + writeFile(outp, d.examples) let backend = if isDefined(d.conf, "js"): "js" elif isDefined(d.conf, "cpp"): "cpp" elif isDefined(d.conf, "objc"): "objc" @@ -400,11 +408,9 @@ proc extractImports(n: PNode; result: PNode) = for i in 0.. Date: Fri, 4 Jan 2019 15:13:29 +0530 Subject: Undocument --genmapping (#10175) --- doc/advopt.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/advopt.txt b/doc/advopt.txt index 75260baad..5ddd064ef 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -55,8 +55,6 @@ Advanced options: --clibdir:DIR modify the linker library search path --clib:LIBNAME link an additional C library (you should omit platform-specific extensions) - --genMapping generate a mapping file containing - (Nim, mangled) identifier pairs --project document the whole project (doc2) --docSeeSrcUrl:url activate 'see source' for doc and doc2 commands (see doc.item.seesrc in config/nimdoc.cfg) -- cgit 1.4.1-2-gfad0 From 69149a0e925d641c35e677a72b356e7f546e7e99 Mon Sep 17 00:00:00 2001 From: Miran Date: Fri, 4 Jan 2019 13:20:12 +0100 Subject: [backport] improve unicode docs, fixes #2353 (#10174) * as instructed in #2353, provides a short description why there are no specialized procs for seq[Rune] * adds several examples to better explain what some functions do * small fixes (double backticks, add missing dots, etc.) * use `rune` instead of "unicode characer" --- lib/pure/unicode.nim | 207 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 122 insertions(+), 85 deletions(-) diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index 712cc46c8..27eabecc6 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -8,6 +8,11 @@ # ## This module provides support to handle the Unicode UTF-8 encoding. +## +## There are no specialized ``insert``, ``delete``, ``add`` and ``contains`` +## procedures for ``seq[Rune]`` in this module because the generic variants +## of these procedures in the system module already work with it. + {.deadCodeElim: on.} # dce option deprecated @@ -15,7 +20,7 @@ include "system/inclrtl" type RuneImpl = int32 # underlying type of Rune - Rune* = distinct RuneImpl ## type that can hold any Unicode character + Rune* = distinct RuneImpl ## Unicode code point. Can hold any Unicode character. Rune16* = distinct int16 ## 16 bit Unicode character proc `<=%`*(a, b: Rune): bool = return int(a) <=% int(b) @@ -25,7 +30,12 @@ proc `==`*(a, b: Rune): bool = return int(a) == int(b) template ones(n: untyped): untyped = ((1 shl n)-1) proc runeLen*(s: string): int {.rtl, extern: "nuc$1".} = - ## Returns the number of Unicode characters of the string ``s`` + ## Returns the number of runes of the string ``s``. + runnableExamples: + let a = "añyóng" + doAssert a.runeLen == 6 + ## note: a.len == 8 + var i = 0 while i < len(s): if ord(s[i]) <=% 127: inc(i) @@ -38,7 +48,12 @@ proc runeLen*(s: string): int {.rtl, extern: "nuc$1".} = inc(result) proc runeLenAt*(s: string, i: Natural): int = - ## Returns the number of bytes the rune starting at ``s[i]`` takes + ## Returns the number of bytes the rune starting at ``s[i]`` takes. + runnableExamples: + let a = "añyóng" + doAssert a.runeLenAt(0) == 1 + doAssert a.runeLenAt(1) == 2 + if ord(s[i]) <=% 127: result = 1 elif ord(s[i]) shr 5 == 0b110: result = 2 elif ord(s[i]) shr 4 == 0b1110: result = 3 @@ -50,7 +65,7 @@ proc runeLenAt*(s: string, i: Natural): int = const replRune = Rune(0xFFFD) template fastRuneAt*(s: string, i: int, result: untyped, doInc = true) = - ## Returns the Unicode character ``s[i]`` in ``result``. If ``doInc == true`` + ## Returns the rune ``s[i]`` in ``result``. If ``doInc == true`` ## ``i`` is incremented by the number of bytes that have been processed. bind ones if ord(s[i]) <=% 127: @@ -152,17 +167,21 @@ proc validateUtf8*(s: string): int = return -1 proc runeAt*(s: string, i: Natural): Rune = - ## Returns the unicode character in ``s`` at byte index ``i`` + ## Returns the rune in ``s`` at **byte index** ``i``. + runnableExamples: + let a = "añyóng" + doAssert a.runeAt(1) == "ñ".runeAt(0) + doAssert a.runeAt(2) == "ñ".runeAt(1) + doAssert a.runeAt(3) == "y".runeAt(0) fastRuneAt(s, i, result, false) template fastToUTF8Copy*(c: Rune, s: var string, pos: int, doInc = true) = - ## Copies UTF-8 representation of `c` into the preallocated string `s` - ## starting at position `pos`. If `doInc == true`, `pos` is incremented + ## Copies UTF-8 representation of ``c`` into the preallocated string ``s`` + ## starting at position ``pos``. If ``doInc == true``, ``pos`` is incremented ## by the number of bytes that have been processed. ## - ## To be the most efficient, make sure `s` is preallocated - ## with an additional amount equal to the byte length of - ## `c`. + ## To be the most efficient, make sure ``s`` is preallocated + ## with an additional amount equal to the byte length of ``c``. var i = RuneImpl(c) if i <=% 127: s.setLen(pos+1) @@ -207,28 +226,39 @@ template fastToUTF8Copy*(c: Rune, s: var string, pos: int, doInc = true) = discard # error, exception? proc toUTF8*(c: Rune): string {.rtl, extern: "nuc$1".} = - ## Converts a rune into its UTF-8 representation + ## Converts a rune into its UTF-8 representation. + runnableExamples: + let a = "añyóng" + doAssert a.runeAt(1).toUTF8 == "ñ" + result = "" fastToUTF8Copy(c, result, 0, false) proc add*(s: var string; c: Rune) = + ## Adds a rune ``c`` to a string ``s``. + runnableExamples: + var s = "abc" + let c = "ä".runeAt(0) + s.add(c) + doAssert s == "abcä" + let pos = s.len fastToUTF8Copy(c, s, pos, false) proc `$`*(rune: Rune): string = - ## Converts a Rune to a string + ## An alias for `toUTF8 <#toUTF8%2CRune>`_. rune.toUTF8 proc `$`*(runes: seq[Rune]): string = - ## Converts a sequence of Runes to a string + ## Converts a sequence of Runes to a string. result = "" for rune in runes: result.add rune proc runeOffset*(s: string, pos:Natural, start: Natural = 0): int = - ## Returns the byte position of unicode character - ## at position pos in s with an optional start byte position. - ## returns the special value -1 if it runs out of the string + ## Returns the byte position of rune + ## at position ``pos`` in ``s`` with an optional start byte position. + ## Returns the special value -1 if it runs out of the string. ## ## Beware: This can lead to unoptimized code and slow execution! ## Most problems can be solved more efficiently by using an iterator @@ -244,7 +274,7 @@ proc runeOffset*(s: string, pos:Natural, start: Natural = 0): int = return o proc runeAtPos*(s: string, pos: int): Rune = - ## Returns the unicode character at position pos + ## Returns the rune at position ``pos``. ## ## Beware: This can lead to unoptimized code and slow execution! ## Most problems can be solved more efficiently by using an iterator @@ -252,7 +282,7 @@ proc runeAtPos*(s: string, pos: int): Rune = fastRuneAt(s, runeOffset(s, pos), result, false) proc runeStrAtPos*(s: string, pos: Natural): string = - ## Returns the unicode character at position pos as UTF8 String + ## Returns the rune at position ``pos`` as UTF8 String. ## ## Beware: This can lead to unoptimized code and slow execution! ## Most problems can be solved more efficiently by using an iterator @@ -262,7 +292,7 @@ proc runeStrAtPos*(s: string, pos: Natural): string = proc runeReverseOffset*(s: string, rev:Positive): (int, int) = ## Returns a tuple with the the byte offset of the - ## unicode character at position ``rev`` in s counting + ## rune at position ``rev`` in ``s``, counting ## from the end (starting with 1) and the total ## number of runes in the string. Returns a negative value ## for offset if there are to few runes in the string to @@ -286,13 +316,21 @@ proc runeReverseOffset*(s: string, rev:Positive): (int, int) = return (-a, rev.int-a) return (x, -a+rev.int) -proc runeSubStr*(s: string, pos:int, len:int = int.high): string = - ## Returns the UTF-8 substring starting at codepoint pos - ## with len codepoints. If pos or len is negative they count from - ## the end of the string. If len is not given it means the longest +proc runeSubStr*(s: string, pos: int, len: int = int.high): string = + ## Returns the UTF-8 substring starting at codepoint ``pos`` + ## with ``len`` codepoints. If ``pos`` or ``len`` is negative they count from + ## the end of the string. If ``len`` is not given it means the longest ## possible string. ## - ## (Needs some examples) + runnableExamples: + let s = "Hänsel ««: 10,00€" + doAssert(runeSubStr(s, 0, 2) == "Hä") + doAssert(runeSubStr(s, 10, 1) == ":") + doAssert(runeSubStr(s, -6) == "10,00€") + doAssert(runeSubStr(s, 10) == ": 10,00€") + doAssert(runeSubStr(s, 12, 5) == "10,00") + doAssert(runeSubStr(s, -6, 3) == "10,") + if pos < 0: let (o, rl) = runeReverseOffset(s, -pos) if len >= rl: @@ -1321,7 +1359,7 @@ proc binarySearch(c: RuneImpl, tab: openArray[int], len, stride: int): int = return -1 proc toLower*(c: Rune): Rune {.rtl, extern: "nuc$1", procvar.} = - ## Converts ``c`` into lower case. This works for any Unicode character. + ## Converts ``c`` into lower case. This works for any rune. ## If possible, prefer ``toLower`` over ``toUpper``. var c = RuneImpl(c) var p = binarySearch(c, tolowerRanges, len(tolowerRanges) div 3, 3) @@ -1333,7 +1371,7 @@ proc toLower*(c: Rune): Rune {.rtl, extern: "nuc$1", procvar.} = return Rune(c) proc toUpper*(c: Rune): Rune {.rtl, extern: "nuc$1", procvar.} = - ## Converts ``c`` into upper case. This works for any Unicode character. + ## Converts ``c`` into upper case. This works for any rune. ## If possible, prefer ``toLower`` over ``toUpper``. var c = RuneImpl(c) var p = binarySearch(c, toupperRanges, len(toupperRanges) div 3, 3) @@ -1345,7 +1383,7 @@ proc toUpper*(c: Rune): Rune {.rtl, extern: "nuc$1", procvar.} = return Rune(c) proc toTitle*(c: Rune): Rune {.rtl, extern: "nuc$1", procvar.} = - ## Converts ``c`` to title case + ## Converts ``c`` to title case. var c = RuneImpl(c) var p = binarySearch(c, toTitleSinglets, len(toTitleSinglets) div 2, 2) if p >= 0 and c == toTitleSinglets[p]: @@ -1353,7 +1391,7 @@ proc toTitle*(c: Rune): Rune {.rtl, extern: "nuc$1", procvar.} = return Rune(c) proc isLower*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = - ## Returns true iff ``c`` is a lower case Unicode character. + ## Returns true iff ``c`` is a lower case rune. ## If possible, prefer ``isLower`` over ``isUpper``. var c = RuneImpl(c) # Note: toUpperRanges is correct here! @@ -1365,7 +1403,7 @@ proc isLower*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = return true proc isUpper*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = - ## Returns true iff ``c`` is a upper case Unicode character. + ## Returns true iff ``c`` is a upper case rune. ## If possible, prefer ``isLower`` over ``isUpper``. var c = RuneImpl(c) # Note: toLowerRanges is correct here! @@ -1377,7 +1415,7 @@ proc isUpper*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = return true proc isAlpha*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = - ## Returns true iff ``c`` is an *alpha* Unicode character (i.e., a letter) + ## Returns true iff ``c`` is an *alpha* rune (i.e., a letter) if isUpper(c) or isLower(c): return true var c = RuneImpl(c) @@ -1389,18 +1427,18 @@ proc isAlpha*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = return true proc isTitle*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = - ## Returns true iff ``c`` is a Unicode titlecase character + ## Returns true iff ``c`` is a Unicode titlecase character. return isUpper(c) and isLower(c) proc isWhiteSpace*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = - ## Returns true iff ``c`` is a Unicode whitespace character + ## Returns true iff ``c`` is a Unicode whitespace character. var c = RuneImpl(c) var p = binarySearch(c, spaceRanges, len(spaceRanges) div 2, 2) if p >= 0 and c >= spaceRanges[p] and c <= spaceRanges[p+1]: return true proc isCombining*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = - ## Returns true iff ``c`` is a Unicode combining character + ## Returns true iff ``c`` is a Unicode combining character. var c = RuneImpl(c) # Optimized to return false immediately for ASCII @@ -1424,12 +1462,12 @@ template runeCheck(s, runeProc) = proc isAlpha*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nuc$1Str".} = - ## Returns true iff `s` contains all alphabetic unicode characters. + ## Returns true iff ``s`` contains all alphabetic runes. runeCheck(s, isAlpha) proc isSpace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nuc$1Str".} = - ## Returns true iff `s` contains all whitespace unicode characters. + ## Returns true iff ``s`` contains all whitespace runes. runeCheck(s, isWhiteSpace) template runeCaseCheck(s, runeProc, skipNonAlpha) = @@ -1459,7 +1497,7 @@ proc isLower*(s: string, skipNonAlpha: bool): bool {. ## Checks whether ``s`` is lower case. ## ## If ``skipNonAlpha`` is true, returns true if all alphabetical - ## runes in ``s`` are lower case. Returns false if none of the + ## runes in ``s`` are lower case. Returns false if none of the ## runes in ``s`` are alphabetical. ## ## If ``skipNonAlpha`` is false, returns true only if all runes in @@ -1474,7 +1512,7 @@ proc isUpper*(s: string, skipNonAlpha: bool): bool {. ## Checks whether ``s`` is upper case. ## ## If ``skipNonAlpha`` is true, returns true if all alphabetical - ## runes in ``s`` are upper case. Returns false if none of the + ## runes in ``s`` are upper case. Returns false if none of the ## runes in ``s`` are alphabetical. ## ## If ``skipNonAlpha`` is false, returns true only if all runes in @@ -1485,7 +1523,7 @@ proc isUpper*(s: string, skipNonAlpha: bool): bool {. runeCaseCheck(s, isUpper, skipNonAlpha) template convertRune(s, runeProc) = - ## Convert runes in `s` using `runeProc` as the converter. + ## Convert runes in ``s`` using ``runeProc`` as the converter. result = newString(len(s)) var @@ -1502,20 +1540,20 @@ template convertRune(s, runeProc) = proc toUpper*(s: string): string {.noSideEffect, procvar, rtl, extern: "nuc$1Str".} = - ## Converts `s` into upper-case unicode characters. + ## Converts ``s`` into upper-case runes. convertRune(s, toUpper) proc toLower*(s: string): string {.noSideEffect, procvar, rtl, extern: "nuc$1Str".} = - ## Converts `s` into lower-case unicode characters. + ## Converts ``s`` into lower-case runes. convertRune(s, toLower) proc swapCase*(s: string): string {.noSideEffect, procvar, rtl, extern: "nuc$1".} = - ## Swaps the case of unicode characters in `s` + ## Swaps the case of runes in ``s``. ## - ## Returns a new string such that the cases of all unicode characters - ## are swapped if possible + ## Returns a new string such that the cases of all runes + ## are swapped if possible. var i = 0 @@ -1538,7 +1576,7 @@ proc swapCase*(s: string): string {.noSideEffect, procvar, proc capitalize*(s: string): string {.noSideEffect, procvar, rtl, extern: "nuc$1".} = - ## Converts the first character of `s` into an upper-case unicode character. + ## Converts the first character of ``s`` into an upper-case rune. if len(s) == 0: return s @@ -1552,10 +1590,10 @@ proc capitalize*(s: string): string {.noSideEffect, procvar, proc translate*(s: string, replacements: proc(key: string): string): string {. rtl, extern: "nuc$1".} = - ## Translates words in a string using the `replacements` proc to substitute - ## words inside `s` with their replacements + ## Translates words in a string using the ``replacements`` proc to substitute + ## words inside ``s`` with their replacements. ## - ## `replacements` is any proc that takes a word and returns + ## ``replacements`` is any proc that takes a word and returns ## a new word to fill it's place. # Allocate memory for the new string based on the old one. @@ -1601,10 +1639,10 @@ proc translate*(s: string, replacements: proc(key: string): string): string {. proc title*(s: string): string {.noSideEffect, procvar, rtl, extern: "nuc$1".} = - ## Converts `s` to a unicode title. + ## Converts ``s`` to a unicode title. ## ## Returns a new string such that the first character - ## in each word inside `s` is capitalized + ## in each word inside ``s`` is capitalized. var i = 0 @@ -1631,10 +1669,10 @@ proc title*(s: string): string {.noSideEffect, procvar, proc isTitle*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nuc$1Str", deprecated: "Deprecated since version 0.20 since its semantics are unclear".}= - ## Checks whether or not `s` is a unicode title. + ## Checks whether or not ``s`` is a unicode title. ## - ## Returns true if the first character in each word inside `s` - ## are upper case and there is at least one character in `s`. + ## Returns true if the first character in each word inside ``s`` + ## are upper case and there is at least one character in ``s``. if s.len == 0: return false @@ -1656,7 +1694,7 @@ proc isTitle*(s: string): bool {.noSideEffect, procvar, firstRune = true iterator runes*(s: string): Rune = - ## Iterates over any unicode character of the string ``s`` returning runes + ## Iterates over any rune of the string ``s`` returning runes. var i = 0 result: Rune @@ -1665,7 +1703,7 @@ iterator runes*(s: string): Rune = yield result iterator utf8*(s: string): string = - ## Iterates over any unicode character of the string ``s`` returning utf8 values + ## Iterates over any rune of the string ``s`` returning utf8 values. var o = 0 while o < s.len: let n = runeLenAt(s, o) @@ -1673,7 +1711,7 @@ iterator utf8*(s: string): string = o += n proc toRunes*(s: string): seq[Rune] = - ## Obtains a sequence containing the Runes in ``s`` + ## Obtains a sequence containing the Runes in ``s``. result = newSeq[Rune]() for r in s.runes: result.add(r) @@ -1696,15 +1734,14 @@ proc cmpRunesIgnoreCase*(a, b: string): int {.rtl, extern: "nuc$1", procvar.} = result = a.len - b.len proc reversed*(s: string): string = - ## Returns the reverse of ``s``, interpreting it as Unicode characters. - ## Unicode combining characters are correctly interpreted as well: - ## - ## .. code-block:: nim - ## - ## assert reversed("Reverse this!") == "!siht esreveR" - ## assert reversed("先秦兩漢") == "漢兩秦先" - ## assert reversed("as⃝df̅") == "f̅ds⃝a" - ## assert reversed("a⃞b⃞c⃞") == "c⃞b⃞a⃞" + ## Returns the reverse of ``s``, interpreting it as runes. + ## Unicode combining characters are correctly interpreted as well. + runnableExamples: + assert reversed("Reverse this!") == "!siht esreveR" + assert reversed("先秦兩漢") == "漢兩秦先" + assert reversed("as⃝df̅") == "f̅ds⃝a" + assert reversed("a⃞b⃞c⃞") == "c⃞b⃞a⃞" + var i = 0 lastI = 0 @@ -1731,7 +1768,7 @@ proc reversed*(s: string): string = reverseUntil(len(s)) proc graphemeLen*(s: string; i: Natural): Natural = - ## The number of bytes belonging to 's[i]' including following combining + ## The number of bytes belonging to ``s[i]`` including following combining ## characters. var j = i.int var r, r2: Rune @@ -1744,7 +1781,7 @@ proc graphemeLen*(s: string; i: Natural): Natural = result = j-i proc lastRune*(s: string; last: int): (Rune, int) = - ## length of the last rune in 's[0..last]'. Returns the rune and its length + ## Length of the last rune in ``s[0..last]``. Returns the rune and its length ## in bytes. if s[last] <= chr(127): result = (Rune(s[last]), 1) @@ -1778,7 +1815,7 @@ proc stringHasSep(s: string, index: int, sep: Rune): bool = return sep == rune template splitCommon(s, sep, maxsplit: untyped, sepLen: int = -1) = - ## Common code for split procedures + ## Common code for split procedures. var last = 0 splits = maxsplit @@ -1801,9 +1838,9 @@ template splitCommon(s, sep, maxsplit: untyped, sepLen: int = -1) = iterator split*(s: string, seps: openarray[Rune] = unicodeSpaces, maxsplit: int = -1): string = - ## Splits the unicode string `s` into substrings using a group of separators. + ## Splits the unicode string ``s`` into substrings using a group of separators. ## - ## Substrings are separated by a substring containing only `seps`. + ## Substrings are separated by a substring containing only ``seps``. ## ## .. code-block:: nim ## for word in split("this\lis an\texample"): @@ -1844,7 +1881,7 @@ iterator split*(s: string, seps: openarray[Rune] = unicodeSpaces, splitCommon(s, seps, maxsplit) iterator splitWhitespace*(s: string): string = - ## Splits a unicode string at whitespace runes + ## Splits a unicode string at whitespace runes. splitCommon(s, unicodeSpaces, -1) template accResult(iter: untyped) = @@ -1858,9 +1895,9 @@ proc splitWhitespace*(s: string): seq[string] {.noSideEffect, accResult(splitWhitespace(s)) iterator split*(s: string, sep: Rune, maxsplit: int = -1): string = - ## Splits the unicode string `s` into substrings using a single separator. + ## Splits the unicode string ``s`` into substrings using a single separator. ## - ## Substrings are separated by the rune `sep`. + ## Substrings are separated by the rune ``sep``. ## The code: ## ## .. code-block:: nim @@ -1898,11 +1935,11 @@ proc split*(s: string, sep: Rune, maxsplit: int = -1): seq[string] {.noSideEffec proc strip*(s: string, leading = true, trailing = true, runes: openarray[Rune] = unicodeSpaces): string {.noSideEffect, rtl, extern: "nucStrip".} = - ## Strips leading or trailing `runes` from `s` and returns + ## Strips leading or trailing ``runes`` from ``s`` and returns ## the resulting string. ## - ## If `leading` is true, leading `runes` are stripped. - ## If `trailing` is true, trailing `runes` are stripped. + ## If ``leading`` is true, leading ``runes`` are stripped. + ## If ``trailing`` is true, trailing ``runes`` are stripped. ## If both are false, the string is returned unchanged. var s_i = 0 ## starting index into string ``s`` @@ -1948,9 +1985,9 @@ proc strip*(s: string, leading = true, trailing = true, proc repeat*(c: Rune, count: Natural): string {.noSideEffect, rtl, extern: "nucRepeatRune".} = - ## Returns a string of `count` Runes `c`. + ## Returns a string of ``count`` Runes ``c``. ## - ## The returned string will have a rune-length of `count`. + ## The returned string will have a rune-length of ``count``. let s = $c result = newStringOfCap(count * s.len) for i in 0 ..< count: @@ -1958,11 +1995,11 @@ proc repeat*(c: Rune, count: Natural): string {.noSideEffect, proc align*(s: string, count: Natural, padding = ' '.Rune): string {. noSideEffect, rtl, extern: "nucAlignString".} = - ## Aligns a unicode string `s` with `padding`, so that it has a rune-length - ## of `count`. + ## Aligns a unicode string ``s`` with ``padding``, so that it has a rune-length + ## of ``count``. ## - ## `padding` characters (by default spaces) are added before `s` resulting in - ## right alignment. If ``s.runelen >= count``, no spaces are added and `s` is + ## ``padding`` characters (by default spaces) are added before ``s`` resulting in + ## right alignment. If ``s.runelen >= count``, no spaces are added and ``s`` is ## returned unchanged. If you need to left align a string use the `alignLeft ## proc <#alignLeft>`_. runnableExamples: @@ -1985,11 +2022,11 @@ proc align*(s: string, count: Natural, padding = ' '.Rune): string {. proc alignLeft*(s: string, count: Natural, padding = ' '.Rune): string {. noSideEffect.} = - ## Left-Aligns a unicode string `s` with `padding`, so that it has a - ## rune-length of `count`. + ## Left-Aligns a unicode string ``s`` with ``padding``, so that it has a + ## rune-length of ``count``. ## - ## `padding` characters (by default spaces) are added after `s` resulting in - ## left alignment. If ``s.runelen >= count``, no spaces are added and `s` is + ## ``padding`` characters (by default spaces) are added after ``s`` resulting in + ## left alignment. If ``s.runelen >= count``, no spaces are added and ``s`` is ## returned unchanged. If you need to right align a string use the `align ## proc <#align>`_. runnableExamples: -- cgit 1.4.1-2-gfad0 From 5101b6befd55a7e32694a09a23dc818cb63dcbb6 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 4 Jan 2019 13:39:47 +0100 Subject: fixes #10188 --- compiler/docgen.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 0fe19c5bc..1af9c06b8 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -382,6 +382,7 @@ proc testExample(d: PDoc; ex: PNode) = d.examples.add "import r\"" & outp.string & "\"\n" proc runAllExamples(d: PDoc) = + if d.examples.len == 0: return let outputDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples" let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" & "_examples.nim")) -- cgit 1.4.1-2-gfad0 From 319b46230cd96fe22a7d95b931e90ccf09783e61 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Fri, 4 Jan 2019 04:54:02 -0800 Subject: fix bug in doAssertRaises when exception==Exception (#10172) * fix bug in doAssertRaises when exception==Exception * add testcase for doAssertRaises --- lib/system.nim | 30 +++++++++++++++++++----------- tests/system/tsystem_misc.nim | 12 ++++++++++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/lib/system.nim b/lib/system.nim index 9fa889b11..d41953108 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -4353,7 +4353,7 @@ else: template runnableExamples*(body: untyped) = discard -template doAssertRaises*(exception, code: untyped): typed = +template doAssertRaises*(exception: typedesc, code: untyped): typed = ## Raises ``AssertionError`` if specified ``code`` does not raise the ## specified exception. Example: ## @@ -4361,16 +4361,24 @@ template doAssertRaises*(exception, code: untyped): typed = ## doAssertRaises(ValueError): ## raise newException(ValueError, "Hello World") var wrong = false - try: - if true: - code - wrong = true - except exception: - discard - except Exception as exc: - raiseAssert(astToStr(exception) & - " wasn't raised, another error was raised instead by:\n"& - astToStr(code)) + when Exception is exception: + try: + if true: + code + wrong = true + except Exception: + discard + else: + try: + if true: + code + wrong = true + except exception: + discard + except Exception as exc: + raiseAssert(astToStr(exception) & + " wasn't raised, another error was raised instead by:\n"& + astToStr(code)) if wrong: raiseAssert(astToStr(exception) & " wasn't raised by:\n" & astToStr(code)) diff --git a/tests/system/tsystem_misc.nim b/tests/system/tsystem_misc.nim index 98bc3f4a3..f53a86e9a 100644 --- a/tests/system/tsystem_misc.nim +++ b/tests/system/tsystem_misc.nim @@ -112,6 +112,18 @@ doAssertRaises(IndexError): foo(toOpenArray(arrNeg, -1, 0)) doAssertRaises(IndexError): foo(toOpenArray(arrNeg, -1, -3)) +doAssertRaises(Exception): + raise newException(Exception, "foo") + +block: + var didThrow = false + try: + doAssertRaises(IndexError): # should fail since it's wrong exception + raise newException(FieldError, "foo") + except AssertionError: + # ok, throwing was correct behavior + didThrow = true + doAssert didThrow type seqqType = ptr UncheckedArray[int] let qData = cast[seqqType](addr seqq[0]) -- cgit 1.4.1-2-gfad0 From 4eee92a7a3741a2783b49dd74c11551cffc5518a Mon Sep 17 00:00:00 2001 From: Tristano Ajmone Date: Fri, 4 Jan 2019 14:37:07 +0100 Subject: Fix Typo in Compiler Guide (#10189) Change 'ableit' to 'albeit'. --- doc/nimc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/nimc.rst b/doc/nimc.rst index e1bf98ece..6f6d38d66 100644 --- a/doc/nimc.rst +++ b/doc/nimc.rst @@ -353,7 +353,7 @@ Define Effect ``useFork`` Makes ``osproc`` use ``fork`` instead of ``posix_spawn``. ``useNimRtl`` Compile and link against ``nimrtl.dll``. ``useMalloc`` Makes Nim use C's `malloc`:idx: instead of Nim's - own memory manager, ableit prefixing each allocation with + own memory manager, albeit prefixing each allocation with its size to support clearing memory on reallocation. This only works with ``gc:none``. ``useRealtimeGC`` Enables support of Nim's GC for *soft* realtime -- cgit 1.4.1-2-gfad0 From ba7d33b4e46f9eafb4980f7a2b4865c5914e0fcc Mon Sep 17 00:00:00 2001 From: rec <44084068+recloser@users.noreply.github.com> Date: Fri, 4 Jan 2019 15:05:03 +0100 Subject: Guard against null exception (#10162) --- lib/system/jssys.nim | 2 +- tests/js/test2.nim | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 8be19e5b8..d7718e4f4 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -46,7 +46,7 @@ proc nimCharToStr(x: char): string {.compilerproc.} = result[0] = x proc isNimException(): bool {.asmNoStackFrame.} = - asm "return `lastJSError`.m_type;" + asm "return `lastJSError` && `lastJSError`.m_type;" proc getCurrentException*(): ref Exception {.compilerRtl, benign.} = if isNimException(): result = cast[ref Exception](lastJSError) diff --git a/tests/js/test2.nim b/tests/js/test2.nim index 0bfb99139..9ecdbb35c 100644 --- a/tests/js/test2.nim +++ b/tests/js/test2.nim @@ -9,6 +9,9 @@ js 3.14 # This file tests the JavaScript generator +doAssert getCurrentException() == nil +doAssert getCurrentExceptionMsg() == "" + # #335 proc foo() = var bar = "foo" -- cgit 1.4.1-2-gfad0 From e9a192c36fb62331db0bbd308433e56fc771c352 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Fri, 4 Jan 2019 19:41:48 +0530 Subject: expandFilename on windows is now consistent with other platforms (#10154) --- compiler/options.nim | 11 +------ lib/pure/os.nim | 90 ++++++++++++++++++++++++++++------------------------ 2 files changed, 49 insertions(+), 52 deletions(-) diff --git a/compiler/options.nim b/compiler/options.nim index 448741ddb..3730d16f8 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -482,16 +482,7 @@ proc setDefaultLibpath*(conf: ConfigRef) = conf.libpath = AbsoluteDir parentNimLibPath proc canonicalizePath*(conf: ConfigRef; path: AbsoluteFile): AbsoluteFile = - # on Windows, 'expandFilename' calls getFullPathName which doesn't do - # case corrections, so we have to use this convoluted way of retrieving - # the true filename (see tests/modules and Nimble uses 'import Uri' instead - # of 'import uri'): - when defined(windows): - result = AbsoluteFile path.string.expandFilename - for x in walkFiles(result.string): - return AbsoluteFile x - else: - result = AbsoluteFile path.string.expandFilename + result = AbsoluteFile path.string.expandFilename proc shortenDir*(conf: ConfigRef; dir: string): string {. deprecated: "use 'relativeTo' instead".} = diff --git a/lib/pure/os.nim b/lib/pure/os.nim index b6cef191b..ab87101c2 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -945,48 +945,6 @@ when not weirdTarget: raise newException(ValueError, "The specified root is not absolute: " & root) joinPath(root, path) -proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", - tags: [ReadDirEffect], noNimScript.} = - ## Returns the full (`absolute`:idx:) path of an existing file `filename`, - ## raises OSError in case of an error. Follows symlinks. - when defined(windows): - var bufsize = MAX_PATH.int32 - when useWinUnicode: - var unused: WideCString = nil - var res = newWideCString("", bufsize) - while true: - var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused) - if L == 0'i32: - raiseOSError(osLastError()) - elif L > bufsize: - res = newWideCString("", L) - bufsize = L - else: - result = res$L - break - else: - var unused: cstring = nil - result = newString(bufsize) - while true: - var L = getFullPathNameA(filename, bufsize, result, unused) - if L == 0'i32: - raiseOSError(osLastError()) - elif L > bufsize: - result = newString(L) - bufsize = L - else: - setLen(result, L) - break - else: - # according to Posix we don't need to allocate space for result pathname. - # But we need to free return value with free(3). - var r = realpath(filename, nil) - if r.isNil: - raiseOSError(osLastError()) - else: - result = $r - c_free(cast[pointer](r)) - proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [], noNimScript.} = ## Normalize a path. ## @@ -1425,6 +1383,54 @@ iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect], noNimScript ## notation is supported. walkCommon(pattern, isDir) +proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", + tags: [ReadDirEffect], noNimScript.} = + ## Returns the full (`absolute`:idx:) path of an existing file `filename`, + ## raises OSError in case of an error. Follows symlinks. + when defined(windows): + var bufsize = MAX_PATH.int32 + when useWinUnicode: + var unused: WideCString = nil + var res = newWideCString("", bufsize) + while true: + var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused) + if L == 0'i32: + raiseOSError(osLastError()) + elif L > bufsize: + res = newWideCString("", L) + bufsize = L + else: + result = res$L + break + else: + var unused: cstring = nil + result = newString(bufsize) + while true: + var L = getFullPathNameA(filename, bufsize, result, unused) + if L == 0'i32: + raiseOSError(osLastError()) + elif L > bufsize: + result = newString(L) + bufsize = L + else: + setLen(result, L) + break + # getFullPathName doesn't do case corrections, so we have to use this convoluted + # way of retrieving the true filename + for x in walkFiles(result.string): + result = x + if not existsFile(result) and not existsDir(result): + raise newException(OSError, "file does not exist") + else: + # according to Posix we don't need to allocate space for result pathname. + # But we need to free return value with free(3). + var r = realpath(filename, nil) + if r.isNil: + raiseOSError(osLastError()) + else: + result = $r + c_free(cast[pointer](r)) + type PathComponent* = enum ## Enumeration specifying a path component. pcFile, ## path refers to a file -- cgit 1.4.1-2-gfad0 From be0a4d1342b05815e25f9a51bda0cf199a6d439e Mon Sep 17 00:00:00 2001 From: alaviss Date: Fri, 4 Jan 2019 21:13:07 +0700 Subject: fix system.nim documentations (#10168) * system: fix nimGC_getStackBottom doc * system/helpers: avoid leaking docs to system --- lib/system.nim | 4 ++-- lib/system/helpers.nim | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/system.nim b/lib/system.nim index d41953108..f6987aa62 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2769,8 +2769,8 @@ when not defined(nimscript) and hasAlloc: when not defined(JS) and not defined(nimscript) and hasAlloc: proc nimGC_setStackBottom*(theStackBottom: pointer) {.compilerRtl, noinline, benign.} - ## Expands operating GC stack range to `theStackBottom`. Does nothing - ## if current stack bottom is already lower than `theStackBottom`. + ## Expands operating GC stack range to `theStackBottom`. Does nothing + ## if current stack bottom is already lower than `theStackBottom`. else: template GC_disable* = diff --git a/lib/system/helpers.nim b/lib/system/helpers.nim index fb1218684..7b2b32679 100644 --- a/lib/system/helpers.nim +++ b/lib/system/helpers.nim @@ -1,5 +1,5 @@ -## helpers used system.nim and other modules, avoids code duplication while -## also minimizing symbols exposed in system.nim +# helpers used system.nim and other modules, avoids code duplication while +# also minimizing symbols exposed in system.nim # # TODO: move other things here that should not be exposed in system.nim -- cgit 1.4.1-2-gfad0 From e5ca57d85c9187d4268c843b564b5123dc2e9960 Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Sat, 5 Jan 2019 01:00:38 +0100 Subject: Fix the tuple returned by `os.splitFile()` in documentation (#10197) --- lib/pure/os.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index ab87101c2..96833056a 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -336,7 +336,7 @@ proc searchExtPos*(path: string): int = proc splitFile*(path: string): tuple[dir, name, ext: string] {. noSideEffect, rtl, extern: "nos$1".} = - ## Splits a filename into (dir, filename, extension). + ## Splits a filename into (dir, name, extension). ## `dir` does not end in `DirSep`. ## `extension` includes the leading dot. ## -- cgit 1.4.1-2-gfad0 From ffea3fbfaed7aaf7c645bf254696ee98c1c840d9 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Sat, 5 Jan 2019 07:01:10 -0800 Subject: * move up runnableExamples definition so can be used more in system.nim (#10196) * document that toInt, toBiggestInt round towards 0 and add runnableExamples * minor doc fixes --- lib/system.nim | 58 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/lib/system.nim b/lib/system.nim index f6987aa62..1d2401940 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -118,6 +118,22 @@ proc defined*(x: untyped): bool {.magic: "Defined", noSideEffect, compileTime.} ## # Do here programmer friendly expensive sanity checks. ## # Put here the normal code +when defined(nimHasRunnableExamples): + proc runnableExamples*(body: untyped) {.magic: "RunnableExamples".} + ## A section you should use to mark `runnable example`:idx: code with. + ## + ## - In normal debug and release builds code within + ## a ``runnableExamples`` section is ignored. + ## - The documentation generator is aware of these examples and considers them + ## part of the ``##`` doc comment. As the last step of documentation + ## generation the examples are put into an ``$file_example.nim`` file, + ## compiled and tested. The collected examples are + ## put into their own module to ensure the examples do not refer to + ## non-exported symbols. +else: + template runnableExamples*(body: untyped) = + discard + proc declared*(x: untyped): bool {.magic: "Defined", noSideEffect, compileTime.} ## Special compile-time procedure that checks whether `x` is ## declared. `x` has to be an identifier or a qualified identifier. @@ -1792,23 +1808,26 @@ proc toFloat*(i: int): float {. proc toBiggestFloat*(i: BiggestInt): BiggestFloat {. magic: "ToBiggestFloat", noSideEffect, importc: "toBiggestFloat".} - ## converts an biggestint `i` into a ``biggestfloat``. If the conversion + ## converts a biggestint `i` into a ``biggestfloat``. If the conversion ## fails, `ValueError` is raised. However, on most platforms the ## conversion cannot fail. proc toInt*(f: float): int {. - magic: "ToInt", noSideEffect, importc: "toInt".} + magic: "ToInt", noSideEffect, importc: "toInt".} = ## converts a floating point number `f` into an ``int``. Conversion - ## rounds `f` if it does not contain an integer value. - ## Note that some floating point numbers (e.g. infinity) cannot be - ## accurately converted. + ## rounds `f` half away from 0, see https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero + ## Note that some floating point numbers (e.g. infinity or even 1e19) + ## cannot be accurately converted. + runnableExamples: + doAssert toInt(0.49) == 0 + doAssert toInt(0.5) == 1 + doAssert toInt(-0.5) == -1 ## rounding is symmetrical proc toBiggestInt*(f: BiggestFloat): BiggestInt {. - magic: "ToBiggestInt", noSideEffect, importc: "toBiggestInt".} - ## converts a biggestfloat `f` into a ``biggestint``. Conversion - ## rounds `f` if it does not contain an integer value. - ## Note that some floating point numbers (e.g. infinity) cannot be - ## accurately converted. + magic: "ToBiggestInt", noSideEffect, importc: "toBiggestInt".} = + ## Same as `toInt` but for BiggestFloat to ``BiggestInt``. + runnableExamples: + doAssert toBiggestInt(0.49) == 0 proc addQuitProc*(quitProc: proc() {.noconv.}) {. importc: "atexit", header: "".} @@ -3084,7 +3103,7 @@ when not defined(JS): #and not defined(nimscript): proc initStackBottom() {.inline, compilerproc.} = # WARNING: This is very fragile! An array size of 8 does not work on my # Linux 64bit system. -- That's because the stack direction is the other - # way round. + # way around. when declared(nimGC_setStackBottom): var locals {.volatile.}: pointer locals = addr(locals) @@ -4336,23 +4355,6 @@ when defined(windows) and appType == "console" and defined(nimSetUtf8CodePage): importc: "SetConsoleOutputCP".} discard setConsoleOutputCP(65001) # 65001 - utf-8 codepage - -when defined(nimHasRunnableExamples): - proc runnableExamples*(body: untyped) {.magic: "RunnableExamples".} - ## A section you should use to mark `runnable example`:idx: code with. - ## - ## - In normal debug and release builds code within - ## a ``runnableExamples`` section is ignored. - ## - The documentation generator is aware of these examples and considers them - ## part of the ``##`` doc comment. As the last step of documentation - ## generation the examples are put into an ``$file_example.nim`` file, - ## compiled and tested. The collected examples are - ## put into their own module to ensure the examples do not refer to - ## non-exported symbols. -else: - template runnableExamples*(body: untyped) = - discard - template doAssertRaises*(exception: typedesc, code: untyped): typed = ## Raises ``AssertionError`` if specified ``code`` does not raise the ## specified exception. Example: -- cgit 1.4.1-2-gfad0 From 4a720394bba39ce1e67d518b909cbb1c25f63d09 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Sat, 5 Jan 2019 07:07:09 -0800 Subject: testament megatest: we can now tell which test failed; helps debugging and prevents certain bugs, plus other fixes (#10089) * [testament] --verboseMegatest flag to make megatest compilation verbose * replace "tests" by testsDir * megatest's nimcache is now in same dir as other tests to avoid clobbering (eg when running tests from multiple Nim repos) --- .gitignore | 4 ++++ testament/categories.nim | 57 +++++++++++++++++++++++++++++++++--------------- testament/tester.nim | 9 +++++--- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index cbfb98a1b..132cd7f60 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,7 @@ test.txt tweeter.db tweeter_test.db megatest.nim + +/outputExpected.txt +/outputGotten.txt + diff --git a/testament/categories.nim b/testament/categories.nim index 55b9e5ed2..4ff50af97 100644 --- a/testament/categories.nim +++ b/testament/categories.nim @@ -97,7 +97,7 @@ proc compileRodFiles(r: var TResults, cat: Category, options: string) = proc flagTests(r: var TResults, cat: Category, options: string) = # --genscript - const filename = "tests"/"flags"/"tgenscript" + const filename = testsDir/"flags"/"tgenscript" const genopts = " --genscript" let nimcache = nimcacheDir(filename, genopts, targetC) testSpec r, makeTest(filename, genopts, cat) @@ -356,7 +356,7 @@ proc testNimInAction(r: var TResults, cat: Category, options: string) = ] for i, test in tests: - let filename = "tests" / test.addFileExt("nim") + let filename = testsDir / test.addFileExt("nim") let testHash = getMD5(readFile(filename).string) doAssert testHash == refHashes[i], "Nim in Action test " & filename & " was changed." # Run the tests. @@ -425,15 +425,12 @@ proc testStdlib(r: var TResults, pattern, options: string, cat: Category) = let contents = readFile(testFile).string var testObj = makeTest(testFile, options, cat) #[ - TODO: + todo: this logic is fragile: false positives (if appears in a comment), or false negatives, eg `when defined(osx) and isMainModule`. - Instead of fixing this, a much better way, is to extend - https://github.com/nim-lang/Nim/issues/9581 to stdlib modules as follows: - * add these to megatest - * patch compiler so `isMainModule` is true when -d:isMainModuleIsAlwaysTrue - That'll give speedup benefit, and we don't have to patch stdlib files. + Instead of fixing this, see https://github.com/nim-lang/Nim/issues/10045 + for a much better way. ]# if "when isMainModule" notin contents: testObj.spec.action = actionCompile @@ -534,7 +531,7 @@ proc `&.?`(a, b: string): string = result = if b.startswith(a): b else: a & b proc processSingleTest(r: var TResults, cat: Category, options, test: string) = - let test = "tests" & DirSep &.? cat.string / test + let test = testsDir &.? cat.string / test let target = if cat.string.normalize == "js": targetJS else: targetC if existsFile(test): testSpec r, makeTest(test, options, cat), {target} @@ -559,6 +556,7 @@ proc isJoinableSpec(spec: TSpec): bool = (spec.targets == {} or spec.targets == {targetC}) proc norm(s: var string) = + # equivalent of s/\n+/\n/g (could use a single pass over input if needed) while true: let tmp = s.replace("\n\n", "\n") if tmp == s: break @@ -566,9 +564,13 @@ proc norm(s: var string) = s = s.strip proc isTestFile*(file: string): bool = - let (dir, name, ext) = splitFile(file) + let (_, name, ext) = splitFile(file) result = ext == ".nim" and name.startsWith("t") +proc quoted(a: string): string = + # todo: consider moving to system.nim + result.addQuoted(a) + proc runJoinedTest(r: var TResults, cat: Category, testsDir: string) = ## returs a list of tests that have problems var specs: seq[TSpec] = @[] @@ -582,6 +584,8 @@ proc runJoinedTest(r: var TResults, cat: Category, testsDir: string) = if isJoinableSpec(spec): specs.add spec + proc cmp(a: TSpec, b:TSpec): auto = cmp(a.file, b.file) + sort(specs, cmp=cmp) # reproducible order echo "joinable specs: ", specs.len if simulate: @@ -591,19 +595,36 @@ proc runJoinedTest(r: var TResults, cat: Category, testsDir: string) = return var megatest: string - for runSpec in specs: - megatest.add "import r\"" - megatest.add runSpec.file - megatest.add "\"\n" + #[ + TODO(minor): + get from Nim cmd + put outputGotten.txt, outputGotten.txt, megatest.nim there too + delete upon completion, maybe + ]# + var outDir = nimcacheDir(testsDir / "megatest", "", targetC) + const marker = "megatest:processing: " + + for i, runSpec in specs: + let file = runSpec.file + let file2 = outDir / ("megatest_" & $i & ".nim") + # `include` didn't work with `trecmod2.nim`, so using `import` + let code = "echo \"" & marker & "\", " & quoted(file) & "\n" + createDir(file2.parentDir) + writeFile(file2, code) + megatest.add "import " & quoted(file2) & "\n" + megatest.add "import " & quoted(file) & "\n" writeFile("megatest.nim", megatest) - const args = ["c", "-d:testing", "--listCmd", "megatest.nim"] - var (buf, exitCode) = execCmdEx2(command = compilerPrefix, args = args, options = {poStdErrToStdOut, poUsePath}, input = "") + let args = ["c", "--nimCache:" & outDir, "-d:testing", "--listCmd", "megatest.nim"] + proc onStdout(line: string) = echo line + var (buf, exitCode) = execCmdEx2(command = compilerPrefix, args = args, options = {poStdErrToStdOut, poUsePath}, input = "", + onStdout = if verboseMegatest: onStdout else: nil) if exitCode != 0: echo buf quit("megatest compilation failed") + # Could also use onStdout here. (buf, exitCode) = execCmdEx("./megatest") if exitCode != 0: echo buf @@ -613,6 +634,7 @@ proc runJoinedTest(r: var TResults, cat: Category, testsDir: string) = writeFile("outputGotten.txt", buf) var outputExpected = "" for i, runSpec in specs: + outputExpected.add marker & runSpec.file & "\n" outputExpected.add runSpec.output.strip outputExpected.add '\n' norm outputExpected @@ -621,6 +643,7 @@ proc runJoinedTest(r: var TResults, cat: Category, testsDir: string) = writeFile("outputExpected.txt", outputExpected) discard execShellCmd("diff -uNdr outputExpected.txt outputGotten.txt") echo "output different!" + # outputGotten.txt, outputExpected.txt not removed on purpose for debugging. quit 1 else: echo "output OK" @@ -686,7 +709,7 @@ proc processCategory(r: var TResults, cat: Category, options, testsDir: string, var testsRun = 0 var files: seq[string] - for file in walkDirRec("tests" & DirSep &.? cat.string): + for file in walkDirRec(testsDir &.? cat.string): if isTestFile(file): files.add file files.sort # give reproducible order diff --git a/testament/tester.nim b/testament/tester.nim index 9e5fe5830..1867871c5 100644 --- a/testament/tester.nim +++ b/testament/tester.nim @@ -17,8 +17,10 @@ import var useColors = true var backendLogging = true var simulate = false +var verboseMegatest = false # very verbose but can be useful const + testsDir = "tests" & DirSep resultsFile = "testresults.html" #jsonFile = "testresults.json" # not used Usage = """Usage: @@ -34,6 +36,7 @@ Arguments: arguments are passed to the compiler Options: --print also print results to the console + --verboseMegatest log to stdout megatetest compilation --simulate see what tests would be run but don't run them (for debugging) --failing only show failing/ignored tests --targets:"c c++ js objc" run tests for specified targets (default: all) @@ -83,7 +86,7 @@ proc getFileDir(filename: string): string = if not result.isAbsolute(): result = getCurrentDir() / result -proc execCmdEx2(command: string, args: openarray[string], options: set[ProcessOption], input: string): tuple[ +proc execCmdEx2(command: string, args: openarray[string], options: set[ProcessOption], input: string, onStdout: proc(line: string) = nil): tuple[ output: TaintedString, exitCode: int] {.tags: [ExecIOEffect, ReadIOEffect, RootEffect], gcsafe.} = @@ -103,6 +106,7 @@ proc execCmdEx2(command: string, args: openarray[string], options: set[ProcessOp if outp.readLine(line): result[0].string.add(line.string) result[0].string.add("\n") + if onStdout != nil: onStdout(line.string) else: result[1] = peekExitCode(p) if result[1] != -1: break @@ -517,8 +521,6 @@ else: include categories -const testsDir = "tests" & DirSep - proc main() = os.putenv "NIMTEST_COLOR", "never" os.putenv "NIMTEST_OUTPUT_LVL", "PRINT_FAILURES" @@ -533,6 +535,7 @@ proc main() = while p.kind == cmdLongoption: case p.key.string.normalize of "print", "verbose": optPrintResults = true + of "verbosemegatest": verboseMegatest = true of "failing": optFailing = true of "pedantic": discard "now always enabled" of "targets": -- cgit 1.4.1-2-gfad0 From f3bd3691e796f4be5d522b4d0d1c64e30f985e68 Mon Sep 17 00:00:00 2001 From: Tristano Ajmone Date: Sun, 6 Jan 2019 10:45:24 +0100 Subject: Minor Fixes to Manual Wording (#10214) --- doc/manual.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual.rst b/doc/manual.rst index 4a1ad9d5e..f79811002 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -1587,7 +1587,7 @@ details like this when mixing garbage collected data with unmanaged memory. Not nil annotation ------------------ -All types for that ``nil`` is a valid value can be annotated to +All types for which ``nil`` is a valid value can be annotated to exclude ``nil`` as a valid value with the ``not nil`` annotation: .. code-block:: nim @@ -1664,12 +1664,12 @@ Nim supports these `calling conventions`:idx:\: and another one for the pointer to implicitly passed environment. `stdcall`:idx: - This the stdcall convention as specified by Microsoft. The generated C + This is the stdcall convention as specified by Microsoft. The generated C procedure is declared with the ``__stdcall`` keyword. `cdecl`:idx: The cdecl convention means that a procedure shall use the same convention - as the C compiler. Under windows the generated C procedure is declared with + as the C compiler. Under Windows the generated C procedure is declared with the ``__cdecl`` keyword. `safecall`:idx: -- cgit 1.4.1-2-gfad0 From 2fa35126b09e3487fbb82328238e59b9a4dd6d4c Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Sat, 5 Jan 2019 21:12:53 +0000 Subject: Fix getAddrInfo, add IPPROTO_ICMPV6 Closes #10198 --- lib/posix/posix_linux_amd64_consts.nim | 1 + lib/posix/posix_nintendoswitch_consts.nim | 1 + lib/posix/posix_other_consts.nim | 1 + lib/pure/nativesockets.nim | 7 ++++-- tests/stdlib/tgetaddrinfo.nim | 36 +++++++++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 tests/stdlib/tgetaddrinfo.nim diff --git a/lib/posix/posix_linux_amd64_consts.nim b/lib/posix/posix_linux_amd64_consts.nim index c23005b1e..dfbfe7f64 100644 --- a/lib/posix/posix_linux_amd64_consts.nim +++ b/lib/posix/posix_linux_amd64_consts.nim @@ -295,6 +295,7 @@ const IF_NAMESIZE* = cint(16) const IPPROTO_IP* = cint(0) const IPPROTO_IPV6* = cint(41) const IPPROTO_ICMP* = cint(1) +const IPPROTO_ICMPV6* = cint(58) const IPPROTO_RAW* = cint(255) const IPPROTO_TCP* = cint(6) const IPPROTO_UDP* = cint(17) diff --git a/lib/posix/posix_nintendoswitch_consts.nim b/lib/posix/posix_nintendoswitch_consts.nim index f0c0dd717..1e782d92e 100644 --- a/lib/posix/posix_nintendoswitch_consts.nim +++ b/lib/posix/posix_nintendoswitch_consts.nim @@ -237,6 +237,7 @@ const IF_NAMESIZE* = cint(16) const IPPROTO_IP* = cint(0) const IPPROTO_IPV6* = cint(41) const IPPROTO_ICMP* = cint(1) +const IPPROTO_ICMPV6* = cint(58) const IPPROTO_RAW* = cint(255) const IPPROTO_TCP* = cint(6) const IPPROTO_UDP* = cint(17) diff --git a/lib/posix/posix_other_consts.nim b/lib/posix/posix_other_consts.nim index 2b4b70941..cd5199078 100644 --- a/lib/posix/posix_other_consts.nim +++ b/lib/posix/posix_other_consts.nim @@ -302,6 +302,7 @@ var IF_NAMESIZE* {.importc: "IF_NAMESIZE", header: "".}: cint var IPPROTO_IP* {.importc: "IPPROTO_IP", header: "".}: cint var IPPROTO_IPV6* {.importc: "IPPROTO_IPV6", header: "".}: cint var IPPROTO_ICMP* {.importc: "IPPROTO_ICMP", header: "".}: cint +var IPPROTO_ICMPV6* {.importc: "IPPROTO_ICMPV6", header: "".}: cint var IPPROTO_RAW* {.importc: "IPPROTO_RAW", header: "".}: cint var IPPROTO_TCP* {.importc: "IPPROTO_TCP", header: "".}: cint var IPPROTO_UDP* {.importc: "IPPROTO_UDP", header: "".}: cint diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim index f98f9a444..96c377187 100644 --- a/lib/pure/nativesockets.nim +++ b/lib/pure/nativesockets.nim @@ -71,6 +71,7 @@ type IPPROTO_IPV6, ## Internet Protocol Version 6. Unsupported on Windows. IPPROTO_RAW, ## Raw IP Packets Protocol. Unsupported on Windows. IPPROTO_ICMP ## Control message protocol. Unsupported on Windows. + IPPROTO_ICMPV6 ## Control message protocol for IPv6. Unsupported on Windows. Servent* = object ## information about a service name*: string @@ -154,6 +155,7 @@ when not useWinVersion: of IPPROTO_IPV6: result = posix.IPPROTO_IPV6 of IPPROTO_RAW: result = posix.IPPROTO_RAW of IPPROTO_ICMP: result = posix.IPPROTO_ICMP + of IPPROTO_ICMPV6: result = posix.IPPROTO_ICMPV6 else: proc toInt(domain: Domain): cint = @@ -179,7 +181,7 @@ proc toSockType*(protocol: Protocol): SockType = SOCK_STREAM of IPPROTO_UDP: SOCK_DGRAM - of IPPROTO_IP, IPPROTO_IPV6, IPPROTO_RAW, IPPROTO_ICMP: + of IPPROTO_IP, IPPROTO_IPV6, IPPROTO_RAW, IPPROTO_ICMP, IPPROTO_ICMPV6: SOCK_RAW proc createNativeSocket*(domain: Domain = AF_INET, @@ -255,7 +257,8 @@ proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET, when not defined(freebsd) and not defined(openbsd) and not defined(netbsd) and not defined(android) and not defined(haiku): if domain == AF_INET6: hints.ai_flags = AI_V4MAPPED - var gaiResult = getaddrinfo(address, $port, addr(hints), result) + let socket_port = if sockType == SOCK_RAW: "" else: $port + var gaiResult = getaddrinfo(address, socket_port, addr(hints), result) if gaiResult != 0'i32: when useWinVersion: raiseOSError(osLastError()) diff --git a/tests/stdlib/tgetaddrinfo.nim b/tests/stdlib/tgetaddrinfo.nim new file mode 100644 index 000000000..39102e131 --- /dev/null +++ b/tests/stdlib/tgetaddrinfo.nim @@ -0,0 +1,36 @@ +discard """ + exitcode: 0 + output: "" +""" + +# bug: https://github.com/nim-lang/Nim/issues/10198 + +import nativesockets + +block DGRAM_UDP: + let aiList = getAddrInfo("127.0.0.1", 999.Port, AF_INET, SOCK_DGRAM, IPPROTO_UDP) + doAssert aiList != nil + doAssert aiList.ai_addr != nil + doAssert aiList.ai_addrlen == 16 + doAssert aiList.ai_next == nil + freeAddrInfo aiList + +when defined(posix): + + block RAW_ICMP: + # the port will be ignored + let aiList = getAddrInfo("127.0.0.1", 999.Port, AF_INET, SOCK_RAW, IPPROTO_ICMP) + doAssert aiList != nil + doAssert aiList.ai_addr != nil + doAssert aiList.ai_addrlen == 16 + doAssert aiList.ai_next == nil + freeAddrInfo aiList + + block RAW_ICMPV6: + # the port will be ignored + let aiList = getAddrInfo("::1", 999.Port, AF_INET6, SOCK_RAW, IPPROTO_ICMPV6) + doAssert aiList != nil + doAssert aiList.ai_addr != nil + doAssert aiList.ai_addrlen == 28 + doAssert aiList.ai_next == nil + freeAddrInfo aiList -- cgit 1.4.1-2-gfad0 From ea3e32c2249ee010eab986850ce52c50fdf4c46e Mon Sep 17 00:00:00 2001 From: c-blake Date: Sun, 6 Jan 2019 18:42:49 -0500 Subject: Remove totally unneeded import of hashes. (#10221) --- lib/pure/parsecfg.nim | 2 +- lib/pure/parsesql.nim | 2 +- lib/pure/parsexml.nim | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pure/parsecfg.nim b/lib/pure/parsecfg.nim index b991dd57f..106d59017 100644 --- a/lib/pure/parsecfg.nim +++ b/lib/pure/parsecfg.nim @@ -111,7 +111,7 @@ ## dict.writeConfig("config.ini") import - hashes, strutils, lexbase, streams, tables + strutils, lexbase, streams, tables include "system/inclrtl" diff --git a/lib/pure/parsesql.nim b/lib/pure/parsesql.nim index 20f02e815..f0961829b 100644 --- a/lib/pure/parsesql.nim +++ b/lib/pure/parsesql.nim @@ -11,7 +11,7 @@ ## parser. It parses PostgreSQL syntax and the SQL ANSI standard. import - hashes, strutils, lexbase + strutils, lexbase # ------------------- scanner ------------------------------------------------- diff --git a/lib/pure/parsexml.nim b/lib/pure/parsexml.nim index 0967f7983..953c5cdde 100644 --- a/lib/pure/parsexml.nim +++ b/lib/pure/parsexml.nim @@ -147,7 +147,7 @@ an HTML document contains. ]## import - hashes, strutils, lexbase, streams, unicode + strutils, lexbase, streams, unicode # the parser treats ``
`` as ``

`` -- cgit 1.4.1-2-gfad0 From b7be67349b2677a251aba8fda999a49b7a85af70 Mon Sep 17 00:00:00 2001 From: Leonardo Cecchi Date: Mon, 7 Jan 2019 00:48:55 +0100 Subject: Fix ODBC SQL Error string decoding (#10207) [backport] ODBC Errors were presented to the users as a sequence of characters. I.e.: >test_oracle.exe Error: ['H', 'Y', '0', '0', '0', '\x00', '\x00', ...] test_oracle.nim(15) test_oracle test_oracle.nim(8) test_oracle db_odbc.nim(534) open db_odbc.nim(168) dbError Error: unhandled exception: ODBC Error [DbError] This patch fix the string decoding, creating a real string: >test_oracle.exe Error: HY000 [Oracle][ODBC][Ora]ORA-12541: TNS:no listener test_oracle.nim(15) test_oracle test_oracle.nim(8) test_oracle db_odbc.nim(534) open db_odbc.nim(168) dbError Error: unhandled exception: ODBC Error [DbError] --- lib/impure/db_odbc.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/impure/db_odbc.nim b/lib/impure/db_odbc.nim index 72d7aa800..b7af5128a 100644 --- a/lib/impure/db_odbc.nim +++ b/lib/impure/db_odbc.nim @@ -131,7 +131,7 @@ proc getErrInfo(db: var DbConn): tuple[res: int, ss, ne, msg: string] {. 511.TSqlSmallInt, retSz.addr.PSQLSMALLINT) except: discard - return (res.int, $sqlState, $nativeErr, $errMsg) + return (res.int, $(addr sqlState), $(addr nativeErr), $(addr errMsg)) proc dbError*(db: var DbConn) {. tags: [ReadDbEffect, WriteDbEffect], raises: [DbError] .} = -- cgit 1.4.1-2-gfad0 From 15773c455a404c65fe7ff4e06b09bed5942d2d57 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Mon, 7 Jan 2019 01:50:21 +0200 Subject: Add changelog for the isNil JavaScript change (#10184) Forgot to add an item, breaking change --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index 47642cab5..b3ccc1b2e 100644 --- a/changelog.md +++ b/changelog.md @@ -52,6 +52,8 @@ - splitFile now correctly handles edge cases, see #10047 +- `isNil` is no longer false for undefined in the JavaScript backend: now it's true for both nil and undefined. Use `isNull` or `isUndefined` if you need exact equallity: `isNil` is consistent with `===`, `isNull` and `isUndefined` with `==`. + #### Breaking changes in the compiler - The compiler now implements the "generic symbol prepass" for `when` statements -- cgit 1.4.1-2-gfad0 From e77dd683eb9b6b2b9c2638f4145f8857fece12cb Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Mon, 7 Jan 2019 05:21:17 +0530 Subject: Fix defer not not-working at top level (#10191) --- compiler/sem.nim | 2 -- compiler/semexprs.nim | 2 ++ tests/async/tasyncfilewrite.nim | 3 +-- tests/exception/tdefer1.nim | 10 +--------- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/compiler/sem.nim b/compiler/sem.nim index 8332af346..3763c9b84 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -556,8 +556,6 @@ proc isEmptyTree(n: PNode): bool = else: result = false proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode = - if n.kind == nkDefer: - localError(c.config, n.info, "defer statement not supported at top level") if c.topStmts == 0 and not isImportSystemStmt(c.graph, n): if sfSystemModule notin c.module.flags and not isEmptyTree(n): c.importTable.addSym c.graph.systemModule # import the "System" identifier diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 19d008557..7a124c769 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -2624,6 +2624,8 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkStaticStmt: result = semStaticStmt(c, n) of nkDefer: + if c.currentScope == c.topLevelScope: + localError(c.config, n.info, "defer statement not supported at top level") n.sons[0] = semExpr(c, n.sons[0]) if not n.sons[0].typ.isEmptyType and not implicitlyDiscardable(n.sons[0]): localError(c.config, n.info, "'defer' takes a 'void' expression") diff --git a/tests/async/tasyncfilewrite.nim b/tests/async/tasyncfilewrite.nim index 373b93301..3baf2bbc6 100644 --- a/tests/async/tasyncfilewrite.nim +++ b/tests/async/tasyncfilewrite.nim @@ -9,7 +9,6 @@ import os, asyncfile, asyncdispatch const F = "test_async.txt" removeFile(F) -defer: removeFile(F) let f = openAsync(F, fmWrite) var futs = newSeq[Future[void]]() for i in 1..3: @@ -17,4 +16,4 @@ for i in 1..3: waitFor(all(futs)) f.close() echo readFile(F) - +removeFile(F) diff --git a/tests/exception/tdefer1.nim b/tests/exception/tdefer1.nim index b84ba7681..db46bad27 100644 --- a/tests/exception/tdefer1.nim +++ b/tests/exception/tdefer1.nim @@ -1,6 +1,5 @@ discard """ output: '''hi -hi 1 hi 2 @@ -10,13 +9,6 @@ A''' # bug #1742 -template test(): untyped = - let a = 0 - defer: echo "hi" - a - -let i = test() - import strutils let x = try: parseInt("133a") except: -1 @@ -31,7 +23,7 @@ template atFuncEnd = template testB(): untyped = let a = 0 - defer: echo "hi" # Delete this line to make it work + defer: echo "hi" a proc main = -- cgit 1.4.1-2-gfad0 From be9d1280ae39b64e6c76179a788034ad6c50a52d Mon Sep 17 00:00:00 2001 From: Ico Doornekamp Date: Mon, 7 Jan 2019 00:52:19 +0100 Subject: Added examples to strtabs module (#10160) --- lib/pure/strtabs.nim | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/pure/strtabs.nim b/lib/pure/strtabs.nim index d8a23286a..7bafe1675 100644 --- a/lib/pure/strtabs.nim +++ b/lib/pure/strtabs.nim @@ -12,6 +12,34 @@ ## style-insensitive mode. An efficient string substitution operator ``%`` ## for the string table is also provided. +runnableExamples: + + var t = newStringTable() + t["name"] = "John" + t["city"] = "Monaco" + doAssert t.len == 2 + doAssert t.hasKey "name" + doAssert "name" in t + +## String tables can be created from a table constructor: + +runnableExamples: + var t = {"name": "John", "city": "Monaco"}.newStringTable + + +## When using the style insensitive mode ``modeStyleInsensitive``, +## all letters are compared case insensitively within the ASCII range +## and underscores are ignored. + +runnableExamples: + + var x = newStringTable(modeStyleInsensitive) + x["first_name"] = "John" + x["LastName"] = "Doe" + + doAssert x["firstName"] == "John" + doAssert x["last_name"] == "Doe" + import hashes, strutils @@ -214,6 +242,9 @@ proc newStringTable*(keyValuePairs: varargs[tuple[key, val: string]], proc `%`*(f: string, t: StringTableRef, flags: set[FormatFlag] = {}): string {. rtlFunc, extern: "nstFormat".} = ## The `%` operator for string tables. + runnableExamples: + var t = {"name": "John", "city": "Monaco"}.newStringTable + doAssert "${name} lives in ${city}" % t == "John lives in Monaco" const PatternChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF'} result = "" -- cgit 1.4.1-2-gfad0 From 5345c5b1302e7beca5eb88ed510570e8e4431413 Mon Sep 17 00:00:00 2001 From: Miran Date: Mon, 7 Jan 2019 10:37:49 +0100 Subject: remove deprecated modules (#10215) * removed from `compiler`: * lists (deprecated 2 years ago) * removed from `lib` (all deprecated 3 years ago): * ssl * matchers * httpserver * removed from `lib/deprecated`: * unsigned * actors (and three accompanying tests) * parseurl * moved to `lib/deprecated`: * securehash (the reason for not directly removing - it was deprecated (only) one year ago) --- compiler/lists.nim | 28 -- doc/lib.rst | 14 +- lib/deprecated/core/unsigned.nim | 18 - lib/deprecated/pure/actors.nim | 239 --------- lib/deprecated/pure/actors.nim.cfg | 3 - lib/deprecated/pure/parseurl.nim | 112 ----- lib/deprecated/pure/rawsockets.nim | 14 - lib/deprecated/pure/securehash.nim | 6 + lib/impure/ssl.nim | 99 ---- lib/pure/httpserver.nim | 535 --------------------- lib/pure/matchers.nim | 68 --- lib/pure/securehash.nim | 6 - .../keineschweine/enet_server/enet_server.nim | 2 +- .../manyloc/keineschweine/server/old_dirserver.nim | 2 +- .../manyloc/keineschweine/server/old_sg_server.nim | 2 +- tests/threads/tactors.nim | 13 - tests/threads/tactors2.nim | 25 - tests/threads/trecursive_actor.nim | 20 - tests/tuples/tuple_with_nil.nim | 1 - tools/kochdocs.nim | 4 - 20 files changed, 11 insertions(+), 1200 deletions(-) delete mode 100644 compiler/lists.nim delete mode 100644 lib/deprecated/core/unsigned.nim delete mode 100644 lib/deprecated/pure/actors.nim delete mode 100644 lib/deprecated/pure/actors.nim.cfg delete mode 100644 lib/deprecated/pure/parseurl.nim delete mode 100644 lib/deprecated/pure/rawsockets.nim create mode 100644 lib/deprecated/pure/securehash.nim delete mode 100644 lib/impure/ssl.nim delete mode 100644 lib/pure/httpserver.nim delete mode 100644 lib/pure/matchers.nim delete mode 100644 lib/pure/securehash.nim delete mode 100644 tests/threads/tactors.nim delete mode 100644 tests/threads/tactors2.nim delete mode 100644 tests/threads/trecursive_actor.nim diff --git a/compiler/lists.nim b/compiler/lists.nim deleted file mode 100644 index bfd052204..000000000 --- a/compiler/lists.nim +++ /dev/null @@ -1,28 +0,0 @@ -# -# -# The Nim Compiler -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -# This module is deprecated, don't use it. -# TODO Remove this - -import os - -static: - echo "WARNING: imported deprecated module compiler/lists.nim, use seq ore lists from the standard library" - -proc appendStr*(list: var seq[string]; data: string) {.deprecated.} = - # just use system.add - list.add(data) - -proc includeStr(list: var seq[string]; data: string): bool {.deprecated.} = - if list.contains(data): - result = true - else: - result = false - list.add data - diff --git a/doc/lib.rst b/doc/lib.rst index 89e3cca40..a16bf2677 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -141,9 +141,6 @@ String handling Ropes can represent very long strings efficiently; especially concatenation is done in O(1) instead of O(n). -* `matchers `_ - This module contains various string matchers for email addresses, etc. - * `subexes `_ This module implements advanced string substitution operations. @@ -275,8 +272,8 @@ Internet Protocols and Support module. * `net `_ - This module implements a high-level sockets API. It will replace the - ``sockets`` module in the future. + This module implements a high-level sockets API. It replaces the + ``sockets`` module. * `nativesockets `_ This module implements a low-level sockets API. @@ -456,13 +453,6 @@ Database support for other databases too. -Other ------ - -* `ssl `_ - This module provides an easy to use sockets-style - Nim interface to the OpenSSL library. - Wrappers ======== diff --git a/lib/deprecated/core/unsigned.nim b/lib/deprecated/core/unsigned.nim deleted file mode 100644 index 93a29e1c9..000000000 --- a/lib/deprecated/core/unsigned.nim +++ /dev/null @@ -1,18 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## **Warning:** Since version 0.11.4 this module is deprecated. -## -## This module implemented basic arithmetic operators for unsigned integers. -## These operators are now available in the ``system`` module directly. - -{.deprecated.} - -export `shr`, `shl`, `and`, `or`, `xor`, `==`, `+`, `-`, `*`, `div`, `mod`, - `<=`, `<` diff --git a/lib/deprecated/pure/actors.nim b/lib/deprecated/pure/actors.nim deleted file mode 100644 index 77c67a3e4..000000000 --- a/lib/deprecated/pure/actors.nim +++ /dev/null @@ -1,239 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## `Actor`:idx: support for Nim. An actor is implemented as a thread with -## a channel as its inbox. This module requires the ``--threads:on`` -## command line switch. -## -## Example: -## -## .. code-block:: nim -## -## var -## a: ActorPool[int, void] -## createActorPool(a) -## for i in 0 ..< 300: -## a.spawn(i, proc (x: int) {.thread.} = echo x) -## a.join() -## -## **Note**: This whole module is deprecated. Use `threadpool` and ``spawn`` -## instead. - -{.deprecated.} - -from os import sleep - -type - Task*[In, Out] = object{.pure, final.} ## a task - when Out isnot void: - receiver*: ptr Channel[Out] ## the receiver channel of the response - action*: proc (x: In): Out {.thread.} ## action to execute; - ## sometimes useful - shutDown*: bool ## set to tell an actor to shut-down - data*: In ## the data to process - - Actor[In, Out] = object{.pure, final.} - i: Channel[Task[In, Out]] - t: Thread[ptr Actor[In, Out]] - - PActor*[In, Out] = ptr Actor[In, Out] ## an actor - -proc spawn*[In, Out](action: proc( - self: PActor[In, Out]){.thread.}): PActor[In, Out] = - ## creates an actor; that is a thread with an inbox. The caller MUST call - ## ``join`` because that also frees the actor's associated resources. - result = cast[PActor[In, Out]](allocShared0(sizeof(result[]))) - open(result.i) - createThread(result.t, action, result) - -proc inbox*[In, Out](self: PActor[In, Out]): ptr Channel[In] = - ## gets a pointer to the associated inbox of the actor `self`. - result = addr(self.i) - -proc running*[In, Out](a: PActor[In, Out]): bool = - ## returns true if the actor `a` is running. - result = running(a.t) - -proc ready*[In, Out](a: PActor[In, Out]): bool = - ## returns true if the actor `a` is ready to process new messages. - result = ready(a.i) - -proc join*[In, Out](a: PActor[In, Out]) = - ## joins an actor. - joinThread(a.t) - close(a.i) - deallocShared(a) - -proc recv*[In, Out](a: PActor[In, Out]): Task[In, Out] = - ## receives a task from `a`'s inbox. - result = recv(a.i) - -proc send*[In, Out, X, Y](receiver: PActor[In, Out], msg: In, - sender: PActor[X, Y]) = - ## sends a message to `a`'s inbox. - var t: Task[In, Out] - t.receiver = addr(sender.i) - shallowCopy(t.data, msg) - send(receiver.i, t) - -proc send*[In, Out](receiver: PActor[In, Out], msg: In, - sender: ptr Channel[Out] = nil) = - ## sends a message to `receiver`'s inbox. - var t: Task[In, Out] - t.receiver = sender - shallowCopy(t.data, msg) - send(receiver.i, t) - -proc sendShutdown*[In, Out](receiver: PActor[In, Out]) = - ## send a shutdown message to `receiver`. - var t: Task[In, Out] - t.shutdown = true - send(receiver.i, t) - -proc reply*[In, Out](t: Task[In, Out], m: Out) = - ## sends a message to io's output message box. - when Out is void: - {.error: "you cannot reply to a void outbox".} - assert t.receiver != nil - send(t.receiver[], m) - - -# ----------------- actor pools ---------------------------------------------- - -type - ActorPool*[In, Out] = object{.pure, final.} ## an actor pool - actors: seq[PActor[In, Out]] - when Out isnot void: - outputs: Channel[Out] - -proc `^`*[T](f: ptr Channel[T]): T = - ## alias for 'recv'. - result = recv(f[]) - -proc poolWorker[In, Out](self: PActor[In, Out]) {.thread.} = - while true: - var m = self.recv - if m.shutDown: break - when Out is void: - m.action(m.data) - else: - send(m.receiver[], m.action(m.data)) - #self.reply() - -proc createActorPool*[In, Out](a: var ActorPool[In, Out], poolSize = 4) = - ## creates an actor pool. - newSeq(a.actors, poolSize) - when Out isnot void: - open(a.outputs) - for i in 0 ..< a.actors.len: - a.actors[i] = spawn(poolWorker[In, Out]) - -proc sync*[In, Out](a: var ActorPool[In, Out], polling=50) = - ## waits for every actor of `a` to finish with its work. Currently this is - ## implemented as polling every `polling` ms and has a slight chance - ## of failing since we check for every actor to be in `ready` state and not - ## for messages still in ether. This will change in a later - ## version, however. - var allReadyCount = 0 - while true: - var wait = false - for i in 0..high(a.actors): - if not a.actors[i].i.ready: - wait = true - allReadyCount = 0 - break - if not wait: - # it's possible that some actor sent a message to some other actor but - # both appeared to be non-working as the message takes some time to - # arrive. We assume that this won't take longer than `polling` and - # simply attempt a second time and declare victory then. ;-) - inc allReadyCount - if allReadyCount > 1: break - sleep(polling) - -proc terminate*[In, Out](a: var ActorPool[In, Out]) = - ## terminates each actor in the actor pool `a` and frees the - ## resources attached to `a`. - var t: Task[In, Out] - t.shutdown = true - for i in 0.. send message to the thread which has the least - # messages pending: - var minIdx = -1 - var minVal = high(int) - for i in 0..high(p.actors): - var curr = p.actors[i].i.peek - if curr == 0: - # ok, is ready now: - p.actors[i].i.send(t) - return - if curr < minVal and curr >= 0: - minVal = curr - minIdx = i - if minIdx >= 0: - p.actors[minIdx].i.send(t) - else: - raise newException(DeadThreadError, "cannot send message; thread died") - -proc spawn*[In, Out](p: var ActorPool[In, Out], input: In, - action: proc (input: In): Out {.thread.} - ): ptr Channel[Out] = - ## uses the actor pool to run ``action(input)`` concurrently. - ## `spawn` is guaranteed to not block. - var t: Task[In, Out] - setupTask() - result = addr(p.outputs) - t.receiver = result - schedule() - -proc spawn*[In](p: var ActorPool[In, void], input: In, - action: proc (input: In) {.thread.}) = - ## uses the actor pool to run ``action(input)`` concurrently. - ## `spawn` is guaranteed to not block. - var t: Task[In, void] - setupTask() - schedule() - -when not defined(testing) and isMainModule: - var - a: ActorPool[int, void] - createActorPool(a) - for i in 0 ..< 300: - a.spawn(i, proc (x: int) {.thread.} = echo x) - - when false: - proc treeDepth(n: PNode): int {.thread.} = - var x = a.spawn(treeDepth, n.le) - var y = a.spawn(treeDepth, n.ri) - result = max(^x, ^y) + 1 - - a.join() - - diff --git a/lib/deprecated/pure/actors.nim.cfg b/lib/deprecated/pure/actors.nim.cfg deleted file mode 100644 index c6bb9c545..000000000 --- a/lib/deprecated/pure/actors.nim.cfg +++ /dev/null @@ -1,3 +0,0 @@ -# to shut up the tester: ---threads:on - diff --git a/lib/deprecated/pure/parseurl.nim b/lib/deprecated/pure/parseurl.nim deleted file mode 100644 index b19f6dc85..000000000 --- a/lib/deprecated/pure/parseurl.nim +++ /dev/null @@ -1,112 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2015 Dominik Picheta -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## **Warnings:** This module is deprecated since version 0.10.2. -## Use the `uri `_ module instead. -## -## Parses & constructs URLs. - -{.deprecated.} - -import strutils - -type - Url* = tuple[ ## represents a *Uniform Resource Locator* (URL) - ## any optional component is "" if it does not exist - scheme, username, password, - hostname, port, path, query, anchor: string] - -proc parseUrl*(url: string): Url {.deprecated.} = - var i = 0 - - var scheme, username, password: string = "" - var hostname, port, path, query, anchor: string = "" - - var temp = "" - - if url[i] != '/': # url isn't a relative path - while true: - # Scheme - if url[i] == ':': - if url[i+1] == '/' and url[i+2] == '/': - scheme = temp - temp.setLen(0) - inc(i, 3) # Skip the // - # Authority(username, password) - if url[i] == '@': - username = temp - let colon = username.find(':') - if colon >= 0: - password = username.substr(colon+1) - username = username.substr(0, colon-1) - temp.setLen(0) - inc(i) #Skip the @ - # hostname(subdomain, domain, port) - if url[i] == '/' or url[i] == '\0': - hostname = temp - let colon = hostname.find(':') - if colon >= 0: - port = hostname.substr(colon+1) - hostname = hostname.substr(0, colon-1) - - temp.setLen(0) - break - - temp.add(url[i]) - inc(i) - - if url[i] == '/': inc(i) # Skip the '/' - # Path - while true: - if url[i] == '?': - path = temp - temp.setLen(0) - if url[i] == '#': - if temp[0] == '?': - query = temp - else: - path = temp - temp.setLen(0) - - if url[i] == '\0': - if temp[0] == '?': - query = temp - elif temp[0] == '#': - anchor = temp - else: - path = temp - break - - temp.add(url[i]) - inc(i) - - return (scheme, username, password, hostname, port, path, query, anchor) - -proc `$`*(u: Url): string {.deprecated.} = - ## turns the URL `u` into its string representation. - result = "" - if u.scheme.len > 0: - result.add(u.scheme) - result.add("://") - if u.username.len > 0: - result.add(u.username) - if u.password.len > 0: - result.add(":") - result.add(u.password) - result.add("@") - result.add(u.hostname) - if u.port.len > 0: - result.add(":") - result.add(u.port) - if u.path.len > 0: - result.add("/") - result.add(u.path) - result.add(u.query) - result.add(u.anchor) - diff --git a/lib/deprecated/pure/rawsockets.nim b/lib/deprecated/pure/rawsockets.nim deleted file mode 100644 index 876334f9e..000000000 --- a/lib/deprecated/pure/rawsockets.nim +++ /dev/null @@ -1,14 +0,0 @@ -import nativesockets -export nativesockets - -{.warning: "rawsockets module is deprecated, use nativesockets instead".} - -template newRawSocket*(domain, sockType, protocol: cint): untyped = - {.warning: "newRawSocket is deprecated, use newNativeSocket instead".} - newNativeSocket(domain, sockType, protocol) - -template newRawSocket*(domain: Domain = AF_INET, - sockType: SockType = SOCK_STREAM, - protocol: Protocol = IPPROTO_TCP): untyped = - {.warning: "newRawSocket is deprecated, use newNativeSocket instead".} - newNativeSocket(domain, sockType, protocol) diff --git a/lib/deprecated/pure/securehash.nim b/lib/deprecated/pure/securehash.nim new file mode 100644 index 000000000..c6cde599a --- /dev/null +++ b/lib/deprecated/pure/securehash.nim @@ -0,0 +1,6 @@ + + +## This module is a deprecated alias for the ``sha1`` module. +{.deprecated.} + +include "../std/sha1" diff --git a/lib/impure/ssl.nim b/lib/impure/ssl.nim deleted file mode 100644 index 0bcb3f217..000000000 --- a/lib/impure/ssl.nim +++ /dev/null @@ -1,99 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Dominik Picheta -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module provides an easy to use sockets-style -## nim interface to the OpenSSL library. -## -## **Warning:** This module is deprecated, use the SSL procedures defined in -## the ``net`` module instead. - -{.deprecated.} - -import openssl, strutils, os - -type - SecureSocket* = object - ssl: SslPtr - bio: BIO - -proc connect*(sock: var SecureSocket, address: string, - port: int): int = - ## Connects to the specified `address` on the specified `port`. - ## Returns the result of the certificate validation. - SslLoadErrorStrings() - ERR_load_BIO_strings() - - if SSL_library_init() != 1: - raiseOSError(osLastError()) - - var ctx = SSL_CTX_new(SSLv23_client_method()) - if ctx == nil: - ERR_print_errors_fp(stderr) - raiseOSError(osLastError()) - - #if SSL_CTX_load_verify_locations(ctx, - # "/tmp/openssl-0.9.8e/certs/vsign1.pem", NIL) == 0: - # echo("Failed load verify locations") - # ERR_print_errors_fp(stderr) - - sock.bio = BIO_new_ssl_connect(ctx) - if BIO_get_ssl(sock.bio, addr(sock.ssl)) == 0: - raiseOSError(osLastError()) - - if BIO_set_conn_hostname(sock.bio, address & ":" & $port) != 1: - raiseOSError(osLastError()) - - if BIO_do_connect(sock.bio) <= 0: - ERR_print_errors_fp(stderr) - raiseOSError(osLastError()) - - result = SSL_get_verify_result(sock.ssl) - -proc recvLine*(sock: SecureSocket, line: var TaintedString): bool = - ## Acts in a similar fashion to the `recvLine` in the sockets module. - ## Returns false when no data is available to be read. - ## `Line` must be initialized and not nil! - setLen(line.string, 0) - while true: - var c: array[0..0, char] - var n = BIO_read(sock.bio, addr c, c.len.cint) - if n <= 0: return false - if c[0] == '\r': - n = BIO_read(sock.bio, addr c, c.len.cint) - if n > 0 and c[0] == '\L': - return true - elif n <= 0: - return false - elif c[0] == '\L': return true - add(line.string, c[0]) - - -proc send*(sock: SecureSocket, data: string) = - ## Writes `data` to the socket. - if BIO_write(sock.bio, data, data.len.cint) <= 0: - raiseOSError(osLastError()) - -proc close*(sock: SecureSocket) = - ## Closes the socket - if BIO_free(sock.bio) <= 0: - ERR_print_errors_fp(stderr) - raiseOSError(osLastError()) - -when not defined(testing) and isMainModule: - var s: SecureSocket - echo connect(s, "smtp.gmail.com", 465) - - #var buffer: array[0..255, char] - #echo BIO_read(bio, buffer, buffer.len) - var buffer: string = "" - - echo s.recvLine(buffer) - echo buffer - echo buffer.len - diff --git a/lib/pure/httpserver.nim b/lib/pure/httpserver.nim deleted file mode 100644 index a81e8c0a8..000000000 --- a/lib/pure/httpserver.nim +++ /dev/null @@ -1,535 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf, Dominik Picheta -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements a simple HTTP-Server. -## -## **Warning**: This module will soon be deprecated in favour of -## the ``asyncdispatch`` module, you should use it instead. -## -## Example: -## -## .. code-block:: nim -## import strutils, sockets, httpserver -## -## var counter = 0 -## proc handleRequest(client: Socket, path, query: string): bool {.procvar.} = -## inc(counter) -## client.send("Hello for the $#th time." % $counter & wwwNL) -## return false # do not stop processing -## -## run(handleRequest, Port(80)) -## - -import parseutils, strutils, os, osproc, strtabs, streams, sockets, asyncio - -const - wwwNL* = "\r\L" - ServerSig = "Server: httpserver.nim/1.0.0" & wwwNL - -# --------------- output messages -------------------------------------------- - -proc sendTextContentType(client: Socket) = - send(client, "Content-type: text/html" & wwwNL) - send(client, wwwNL) - -proc sendStatus(client: Socket, status: string) = - send(client, "HTTP/1.1 " & status & wwwNL) - -proc badRequest(client: Socket) = - # Inform the client that a request it has made has a problem. - send(client, "HTTP/1.1 400 Bad Request" & wwwNL) - sendTextContentType(client) - send(client, "

Your browser sent a bad request, " & - "such as a POST without a Content-Length.

" & wwwNL) - -when false: - proc cannotExec(client: Socket) = - send(client, "HTTP/1.1 500 Internal Server Error" & wwwNL) - sendTextContentType(client) - send(client, "

Error prohibited CGI execution." & wwwNL) - -proc headers(client: Socket, filename: string) = - # XXX could use filename to determine file type - send(client, "HTTP/1.1 200 OK" & wwwNL) - send(client, ServerSig) - sendTextContentType(client) - -proc notFound(client: Socket) = - send(client, "HTTP/1.1 404 NOT FOUND" & wwwNL) - send(client, ServerSig) - sendTextContentType(client) - send(client, "Not Found" & wwwNL) - send(client, "

The server could not fulfill" & wwwNL) - send(client, "your request because the resource specified" & wwwNL) - send(client, "is unavailable or nonexistent.

" & wwwNL) - send(client, "" & wwwNL) - -proc unimplemented(client: Socket) = - send(client, "HTTP/1.1 501 Method Not Implemented" & wwwNL) - send(client, ServerSig) - sendTextContentType(client) - send(client, "Method Not Implemented" & - "" & - "

HTTP request method not supported.

" & - "" & wwwNL) - -# ----------------- file serving --------------------------------------------- - -when false: - proc discardHeaders(client: Socket) = skip(client) - -proc serveFile*(client: Socket, filename: string) = - ## serves a file to the client. - var f: File - if open(f, filename): - headers(client, filename) - const bufSize = 8000 # != 8K might be good for memory manager - var buf = alloc(bufsize) - while true: - var bytesread = readBuffer(f, buf, bufsize) - if bytesread > 0: - var byteswritten = send(client, buf, bytesread) - if bytesread != bytesWritten: - dealloc(buf) - close(f) - raiseOSError(osLastError()) - if bytesread != bufSize: break - dealloc(buf) - close(f) - else: - notFound(client) - -# ------------------ CGI execution ------------------------------------------- -when false: - # TODO: Fix this, or get rid of it. - type - RequestMethod = enum reqGet, reqPost - - proc executeCgi(client: Socket, path, query: string, meth: RequestMethod) = - var env = newStringTable(modeCaseInsensitive) - var contentLength = -1 - case meth - of reqGet: - discardHeaders(client) - - env["REQUEST_METHOD"] = "GET" - env["QUERY_STRING"] = query - of reqPost: - var buf = TaintedString"" - var dataAvail = false - while dataAvail: - dataAvail = recvLine(client, buf) # TODO: This is incorrect. - var L = toLowerAscii(buf.string) - if L.startsWith("content-length:"): - var i = len("content-length:") - while L[i] in Whitespace: inc(i) - contentLength = parseInt(substr(L, i)) - - if contentLength < 0: - badRequest(client) - return - - env["REQUEST_METHOD"] = "POST" - env["CONTENT_LENGTH"] = $contentLength - - send(client, "HTTP/1.0 200 OK" & wwwNL) - - var process = startProcess(command=path, env=env) - if meth == reqPost: - # get from client and post to CGI program: - var buf = alloc(contentLength) - if recv(client, buf, contentLength) != contentLength: - dealloc(buf) - raiseOSError() - var inp = process.inputStream - inp.writeData(buf, contentLength) - dealloc(buf) - - var outp = process.outputStream - var line = newStringOfCap(120).TaintedString - while true: - if outp.readLine(line): - send(client, line.string) - send(client, wwwNL) - elif not running(process): break - - # --------------- Server Setup ----------------------------------------------- - - proc acceptRequest(client: Socket) = - var cgi = false - var query = "" - var buf = TaintedString"" - discard recvLine(client, buf) - var path = "" - var data = buf.string.split() - var meth = reqGet - - var q = find(data[1], '?') - - # extract path - if q >= 0: - # strip "?..." from path, this may be found in both POST and GET - path = "." & data[1].substr(0, q-1) - else: - path = "." & data[1] - # path starts with "/", by adding "." in front of it we serve files from cwd - - if cmpIgnoreCase(data[0], "GET") == 0: - if q >= 0: - cgi = true - query = data[1].substr(q+1) - elif cmpIgnoreCase(data[0], "POST") == 0: - cgi = true - meth = reqPost - else: - unimplemented(client) - - if path[path.len-1] == '/' or existsDir(path): - path = path / "index.html" - - if not existsFile(path): - discardHeaders(client) - notFound(client) - else: - when defined(Windows): - var ext = splitFile(path).ext.toLowerAscii - if ext == ".exe" or ext == ".cgi": - # XXX: extract interpreter information here? - cgi = true - else: - if {fpUserExec, fpGroupExec, fpOthersExec} * path.getFilePermissions != {}: - cgi = true - if not cgi: - serveFile(client, path) - else: - executeCgi(client, path, query, meth) - -type - Server* = object of RootObj ## contains the current server state - socket: Socket - port: Port - client*: Socket ## the socket to write the file data to - reqMethod*: string ## Request method. GET or POST. - path*, query*: string ## path and query the client requested - headers*: StringTableRef ## headers with which the client made the request - body*: string ## only set with POST requests - ip*: string ## ip address of the requesting client - - PAsyncHTTPServer* = ref AsyncHTTPServer - AsyncHTTPServer = object of Server - asyncSocket: AsyncSocket - -proc open*(s: var Server, port = Port(80), reuseAddr = false) = - ## creates a new server at port `port`. If ``port == 0`` a free port is - ## acquired that can be accessed later by the ``port`` proc. - s.socket = socket(AF_INET) - if s.socket == invalidSocket: raiseOSError(osLastError()) - if reuseAddr: - s.socket.setSockOpt(OptReuseAddr, true) - bindAddr(s.socket, port) - listen(s.socket) - - if port == Port(0): - s.port = getSockName(s.socket) - else: - s.port = port - s.client = invalidSocket - s.reqMethod = "" - s.body = "" - s.path = "" - s.query = "" - s.headers = {:}.newStringTable() - -proc port*(s: var Server): Port = - ## get the port number the server has acquired. - result = s.port - -proc next*(s: var Server) = - ## proceed to the first/next request. - var client: Socket - new(client) - var ip: string - acceptAddr(s.socket, client, ip) - s.client = client - s.ip = ip - s.headers = newStringTable(modeCaseInsensitive) - #headers(s.client, "") - var data = "" - s.client.readLine(data) - if data == "": - # Socket disconnected - s.client.close() - next(s) - return - var header = "" - while true: - s.client.readLine(header) - if header == "\c\L": break - if header != "": - var i = 0 - var key = "" - var value = "" - i = header.parseUntil(key, ':') - inc(i) # skip : - i += header.skipWhiteSpace(i) - i += header.parseUntil(value, {'\c', '\L'}, i) - s.headers[key] = value - else: - s.client.close() - next(s) - return - - var i = skipWhitespace(data) - if skipIgnoreCase(data, "GET") > 0: - s.reqMethod = "GET" - inc(i, 3) - elif skipIgnoreCase(data, "POST") > 0: - s.reqMethod = "POST" - inc(i, 4) - else: - unimplemented(s.client) - s.client.close() - next(s) - return - - if s.reqMethod == "POST": - # Check for Expect header - if s.headers.hasKey("Expect"): - if s.headers["Expect"].toLowerAscii == "100-continue": - s.client.sendStatus("100 Continue") - else: - s.client.sendStatus("417 Expectation Failed") - - # Read the body - # - Check for Content-length header - if s.headers.hasKey("Content-Length"): - var contentLength = 0 - if parseInt(s.headers["Content-Length"], contentLength) == 0: - badRequest(s.client) - s.client.close() - next(s) - return - else: - var totalRead = 0 - var totalBody = "" - while totalRead < contentLength: - var chunkSize = 8000 - if (contentLength - totalRead) < 8000: - chunkSize = (contentLength - totalRead) - var bodyData = newString(chunkSize) - var octetsRead = s.client.recv(cstring(bodyData), chunkSize) - if octetsRead <= 0: - s.client.close() - next(s) - return - totalRead += octetsRead - totalBody.add(bodyData) - if totalBody.len != contentLength: - s.client.close() - next(s) - return - - s.body = totalBody - else: - badRequest(s.client) - s.client.close() - next(s) - return - - var L = skipWhitespace(data, i) - inc(i, L) - # XXX we ignore "HTTP/1.1" etc. for now here - var query = 0 - var last = i - while last < data.len and data[last] notin Whitespace: - if data[last] == '?' and query == 0: query = last - inc(last) - if query > 0: - s.query = data.substr(query+1, last-1) - s.path = data.substr(i, query-1) - else: - s.query = "" - s.path = data.substr(i, last-1) - -proc close*(s: Server) = - ## closes the server (and the socket the server uses). - close(s.socket) - -proc run*(handleRequest: proc (client: Socket, - path, query: string): bool {.closure.}, - port = Port(80)) = - ## encapsulates the server object and main loop - var s: Server - open(s, port, reuseAddr = true) - #echo("httpserver running on port ", s.port) - while true: - next(s) - if handleRequest(s.client, s.path, s.query): break - close(s.client) - close(s) - -# -- AsyncIO begin - -proc nextAsync(s: PAsyncHTTPServer) = - ## proceed to the first/next request. - var client: Socket - new(client) - var ip: string - acceptAddr(getSocket(s.asyncSocket), client, ip) - s.client = client - s.ip = ip - s.headers = newStringTable(modeCaseInsensitive) - #headers(s.client, "") - var data = "" - s.client.readLine(data) - if data == "": - # Socket disconnected - s.client.close() - return - var header = "" - while true: - s.client.readLine(header) # TODO: Very inefficient here. Prone to DOS. - if header == "\c\L": break - if header != "": - var i = 0 - var key = "" - var value = "" - i = header.parseUntil(key, ':') - inc(i) # skip : - if i < header.len: - i += header.skipWhiteSpace(i) - i += header.parseUntil(value, {'\c', '\L'}, i) - s.headers[key] = value - else: - s.client.close() - return - - var i = skipWhitespace(data) - if skipIgnoreCase(data, "GET") > 0: - s.reqMethod = "GET" - inc(i, 3) - elif skipIgnoreCase(data, "POST") > 0: - s.reqMethod = "POST" - inc(i, 4) - else: - unimplemented(s.client) - s.client.close() - return - - if s.reqMethod == "POST": - # Check for Expect header - if s.headers.hasKey("Expect"): - if s.headers["Expect"].toLowerAscii == "100-continue": - s.client.sendStatus("100 Continue") - else: - s.client.sendStatus("417 Expectation Failed") - - # Read the body - # - Check for Content-length header - if s.headers.hasKey("Content-Length"): - var contentLength = 0 - if parseInt(s.headers["Content-Length"], contentLength) == 0: - badRequest(s.client) - s.client.close() - return - else: - var totalRead = 0 - var totalBody = "" - while totalRead < contentLength: - var chunkSize = 8000 - if (contentLength - totalRead) < 8000: - chunkSize = (contentLength - totalRead) - var bodyData = newString(chunkSize) - var octetsRead = s.client.recv(cstring(bodyData), chunkSize) - if octetsRead <= 0: - s.client.close() - return - totalRead += octetsRead - totalBody.add(bodyData) - if totalBody.len != contentLength: - s.client.close() - return - - s.body = totalBody - else: - badRequest(s.client) - s.client.close() - return - - var L = skipWhitespace(data, i) - inc(i, L) - # XXX we ignore "HTTP/1.1" etc. for now here - var query = 0 - var last = i - while last < data.len and data[last] notin Whitespace: - if data[last] == '?' and query == 0: query = last - inc(last) - if query > 0: - s.query = data.substr(query+1, last-1) - s.path = data.substr(i, query-1) - else: - s.query = "" - s.path = data.substr(i, last-1) - -proc asyncHTTPServer*(handleRequest: proc (server: PAsyncHTTPServer, client: Socket, - path, query: string): bool {.closure, gcsafe.}, - port = Port(80), address = "", - reuseAddr = false): PAsyncHTTPServer = - ## Creates an Asynchronous HTTP server at ``port``. - var capturedRet: PAsyncHTTPServer - new(capturedRet) - capturedRet.asyncSocket = asyncSocket() - capturedRet.asyncSocket.handleAccept = - proc (s: AsyncSocket) = - nextAsync(capturedRet) - let quit = handleRequest(capturedRet, capturedRet.client, capturedRet.path, - capturedRet.query) - if quit: capturedRet.asyncSocket.close() - if reuseAddr: - capturedRet.asyncSocket.setSockOpt(OptReuseAddr, true) - - capturedRet.asyncSocket.bindAddr(port, address) - capturedRet.asyncSocket.listen() - if port == Port(0): - capturedRet.port = getSockName(capturedRet.asyncSocket) - else: - capturedRet.port = port - - capturedRet.client = invalidSocket - capturedRet.reqMethod = "" - capturedRet.body = "" - capturedRet.path = "" - capturedRet.query = "" - capturedRet.headers = {:}.newStringTable() - result = capturedRet - -proc register*(d: Dispatcher, s: PAsyncHTTPServer) = - ## Registers a ``PAsyncHTTPServer`` with a ``Dispatcher``. - d.register(s.asyncSocket) - -proc close*(h: PAsyncHTTPServer) = - ## Closes the ``PAsyncHTTPServer``. - h.asyncSocket.close() - -when not defined(testing) and isMainModule: - var counter = 0 - - var s: Server - open(s, Port(0)) - echo("httpserver running on port ", s.port) - while true: - next(s) - - inc(counter) - s.client.send("Hello, Andreas, for the $#th time. $# ? $#" % [ - $counter, s.path, s.query] & wwwNL) - - close(s.client) - close(s) - diff --git a/lib/pure/matchers.nim b/lib/pure/matchers.nim deleted file mode 100644 index 97223ed01..000000000 --- a/lib/pure/matchers.nim +++ /dev/null @@ -1,68 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2015 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module contains various string matchers for email addresses, etc. -## -## **Warning:** This module is deprecated since version 0.14.0. -{.deprecated.} - -{.deadCodeElim: on.} # dce option deprecated - -{.push debugger:off .} # the user does not want to trace a part - # of the standard library! - -include "system/inclrtl" - -import parseutils, strutils - -proc validEmailAddress*(s: string): bool {.noSideEffect, - rtl, extern: "nsuValidEmailAddress".} = - ## returns true if `s` seems to be a valid e-mail address. - ## The checking also uses a domain list. - const - chars = Letters + Digits + {'!','#','$','%','&', - '\'','*','+','/','=','?','^','_','`','{','}','|','~','-','.'} - var i = 0 - if i >= s.len or s[i] notin chars or s[i] == '.': return false - while i < s.len and s[i] in chars: - if i+1 < s.len and s[i] == '.' and s[i+1] == '.': return false - inc(i) - if i >= s.len or s[i] != '@': return false - var j = len(s)-1 - if j >= 0 and s[j] notin Letters: return false - while j >= i and s[j] in Letters: dec(j) - inc(i) # skip '@' - while i < s.len and s[i] in {'0'..'9', 'a'..'z', '-', '.'}: inc(i) - if i != s.len: return false - - var x = substr(s, j+1) - if len(x) == 2 and x[0] in Letters and x[1] in Letters: return true - case toLowerAscii(x) - of "com", "org", "net", "gov", "mil", "biz", "info", "mobi", "name", - "aero", "jobs", "museum": return true - else: return false - -proc parseInt*(s: string, value: var int, validRange: HSlice[int, int]) {. - noSideEffect, rtl, extern: "nmatchParseInt".} = - ## parses `s` into an integer in the range `validRange`. If successful, - ## `value` is modified to contain the result. Otherwise no exception is - ## raised and `value` is not touched; this way a reasonable default value - ## won't be overwritten. - var x = value - try: - discard parseutils.parseInt(s, x, 0) - except OverflowError: - discard - if x in validRange: value = x - -when isMainModule: - doAssert "wuseldusel@codehome.com".validEmailAddress - -{.pop.} - diff --git a/lib/pure/securehash.nim b/lib/pure/securehash.nim deleted file mode 100644 index c6cde599a..000000000 --- a/lib/pure/securehash.nim +++ /dev/null @@ -1,6 +0,0 @@ - - -## This module is a deprecated alias for the ``sha1`` module. -{.deprecated.} - -include "../std/sha1" diff --git a/tests/manyloc/keineschweine/enet_server/enet_server.nim b/tests/manyloc/keineschweine/enet_server/enet_server.nim index 3bb259386..336e57755 100644 --- a/tests/manyloc/keineschweine/enet_server/enet_server.nim +++ b/tests/manyloc/keineschweine/enet_server/enet_server.nim @@ -103,7 +103,7 @@ handlers[HZoneJoinReq] = proc(client: PClient; buffer: PBuffer) = when true: - import parseopt, matchers, os, json + import parseopt, os, json if enetInit() != 0: diff --git a/tests/manyloc/keineschweine/server/old_dirserver.nim b/tests/manyloc/keineschweine/server/old_dirserver.nim index cfb0b0377..390b738aa 100644 --- a/tests/manyloc/keineschweine/server/old_dirserver.nim +++ b/tests/manyloc/keineschweine/server/old_dirserver.nim @@ -157,7 +157,7 @@ proc poll*(timeout: int = 250) = c.outputBuf.flush() when true: - import parseopt, matchers, strutils + import parseopt, strutils var cfgFile = "dirserver_settings.json" for kind, key, val in getOpt(): case kind diff --git a/tests/manyloc/keineschweine/server/old_sg_server.nim b/tests/manyloc/keineschweine/server/old_sg_server.nim index d046df9dd..473880e2b 100644 --- a/tests/manyloc/keineschweine/server/old_sg_server.nim +++ b/tests/manyloc/keineschweine/server/old_sg_server.nim @@ -142,7 +142,7 @@ proc poll*(timeout: int = 250) = c.outputBuf.flush() when true: - import parseopt, matchers, strutils + import parseopt, strutils var zoneCfgFile = "./server_settings.json" for kind, key, val in getOpt(): case kind diff --git a/tests/threads/tactors.nim b/tests/threads/tactors.nim deleted file mode 100644 index ea052b9bd..000000000 --- a/tests/threads/tactors.nim +++ /dev/null @@ -1,13 +0,0 @@ -discard """ - outputsub: "150" -""" - -import actors - -var - pool: ActorPool[int, void] -createActorPool(pool) -for i in 0 ..< 300: - pool.spawn(i, proc (x: int) {.thread.} = echo x) -pool.join() - diff --git a/tests/threads/tactors2.nim b/tests/threads/tactors2.nim deleted file mode 100644 index e8afe203c..000000000 --- a/tests/threads/tactors2.nim +++ /dev/null @@ -1,25 +0,0 @@ -discard """ - output: "1" -""" - -import actors - -type - some_type {.pure, final.} = object - bla: int - -proc thread_proc(input: some_type): some_type {.thread.} = - result.bla = 1 - -proc main() = - var actorPool: ActorPool[some_type, some_type] - createActorPool(actorPool, 1) - - var some_data: some_type - - var inchannel = spawn(actorPool, some_data, thread_proc) - var recv_data = ^inchannel - close(inchannel[]) - echo recv_data.bla - -main() diff --git a/tests/threads/trecursive_actor.nim b/tests/threads/trecursive_actor.nim deleted file mode 100644 index d7072aa53..000000000 --- a/tests/threads/trecursive_actor.nim +++ /dev/null @@ -1,20 +0,0 @@ -discard """ - disabled: yes - outputsub: "0" -""" - -import actors - -var - a: TActorPool[int, void] -createActorPool(a) - -proc task(i: int) {.thread.} = - echo i - if i != 0: a.spawn (i-1, task) - -# count from 9 till 0 and check 0 is somewhere in the output -a.spawn(9, task) -a.join() - - diff --git a/tests/tuples/tuple_with_nil.nim b/tests/tuples/tuple_with_nil.nim index e09c53407..1b210b2bf 100644 --- a/tests/tuples/tuple_with_nil.nim +++ b/tests/tuples/tuple_with_nil.nim @@ -4,7 +4,6 @@ import parseutils import unicode import math import fenv -#import unsigned import pegs import streams diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim index 658b9ead7..6741fcf5d 100644 --- a/tools/kochdocs.nim +++ b/tools/kochdocs.nim @@ -126,7 +126,6 @@ lib/js/asyncjs.nim lib/pure/os.nim lib/pure/strutils.nim lib/pure/math.nim -lib/pure/matchers.nim lib/std/editdistance.nim lib/std/wordwrap.nim lib/experimental/diff.nim @@ -161,10 +160,8 @@ lib/impure/db_mysql.nim lib/impure/db_sqlite.nim lib/impure/db_odbc.nim lib/pure/db_common.nim -lib/pure/httpserver.nim lib/pure/httpclient.nim lib/pure/smtp.nim -lib/impure/ssl.nim lib/pure/ropes.nim lib/pure/unidecode/unidecode.nim lib/pure/xmlparser.nim @@ -218,7 +215,6 @@ lib/pure/selectors.nim lib/pure/sugar.nim lib/pure/collections/chains.nim lib/pure/asyncfile.nim -lib/deprecated/pure/ftpclient.nim lib/pure/asyncftpclient.nim lib/pure/lenientops.nim lib/pure/md5.nim -- cgit 1.4.1-2-gfad0 From 87f8ec5b92d5647ab4b1875262e845d51dd82763 Mon Sep 17 00:00:00 2001 From: zah Date: Mon, 7 Jan 2019 13:10:54 +0200 Subject: Fix #10073 (#10218) --- compiler/semexprs.nim | 9 ++++++++- tests/statictypes/tstatictypes.nim | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 7a124c769..f03af3db9 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -1043,7 +1043,8 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = of skConst: markUsed(c.config, n.info, s, c.graph.usageSym) onUse(n.info, s) - case skipTypes(s.typ, abstractInst-{tyTypeDesc}).kind + let typ = skipTypes(s.typ, abstractInst-{tyTypeDesc}) + case typ.kind of tyNil, tyChar, tyInt..tyInt64, tyFloat..tyFloat128, tyTuple, tySet, tyUInt..tyUInt64: if s.magic == mNone: result = inlineConst(c, n, s) @@ -1061,6 +1062,12 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = # deal with two different ``[]``. if s.ast.len == 0: result = inlineConst(c, n, s) else: result = newSymNode(s, n.info) + of tyStatic: + if typ.n != nil: + result = typ.n + result.typ = typ.base + else: + result = newSymNode(s, n.info) else: result = newSymNode(s, n.info) of skMacro: diff --git a/tests/statictypes/tstatictypes.nim b/tests/statictypes/tstatictypes.nim index 2a3b7332d..9888165cc 100644 --- a/tests/statictypes/tstatictypes.nim +++ b/tests/statictypes/tstatictypes.nim @@ -116,3 +116,19 @@ block: Tensor[B: static[Backend]; T] = object BackProp[B: static[Backend],T] = proc (gradient: Tensor[B,T]): Tensor[B,T] + +# https://github.com/nim-lang/Nim/issues/10073 +block: + proc foo[N: static int](x: var int, + y: int, + z: static int, + arr: array[N, int]): auto = + var t1 = (a: x, b: y, c: z, d: N) + var t2 = (x, y, z, N) + doAssert t1 == t2 + result = t1 + + var y = 20 + var x = foo(y, 10, 15, [1, 2, 3]) + doAssert x == (20, 10, 15, 3) + -- cgit 1.4.1-2-gfad0 From 139fa396e8fa0e8603d4f53ac90841421e50aa3f Mon Sep 17 00:00:00 2001 From: alaviss Date: Mon, 7 Jan 2019 18:27:07 +0700 Subject: os.execShellCmd: use WEXITSTATUS to retrieve exit code (#10222) According to POSIX, system() shall returns the termination status in the format specified by waitpid(), which means WEXITSTATUS should be used to retrieve the exit code portably. This fixes execShellCmd on Haiku. --- lib/pure/os.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 96833056a..a218121ed 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1298,7 +1298,7 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", ## shell involved, use the `execProcess` proc of the `osproc` ## module. when defined(posix): - result = c_system(command) shr 8 + result = WEXITSTATUS(c_system(command)) else: result = c_system(command) -- cgit 1.4.1-2-gfad0 From 044cef152f6006927a905d69dc527cada8206b0f Mon Sep 17 00:00:00 2001 From: jcosborn Date: Mon, 7 Jan 2019 05:36:06 -0600 Subject: add custom pragma support for var and let symbols (#9582) * add custom pragma support for var and let symbols * updated changelog for custom pragmas on var and let symbols * add oldast switch for backwards compatibility --- changelog.md | 10 ++++++---- compiler/ast.nim | 7 +++++++ compiler/commands.nim | 2 ++ compiler/guards.nim | 8 ++++---- compiler/options.nim | 3 ++- compiler/semstmts.nim | 23 +++++++++++++++++++---- doc/advopt.txt | 1 + lib/core/macros.nim | 8 +++++++- tests/pragmas/tcustom_pragma.nim | 35 +++++++++++++++++++++++++++++------ 9 files changed, 77 insertions(+), 20 deletions(-) diff --git a/changelog.md b/changelog.md index b3ccc1b2e..05f65516d 100644 --- a/changelog.md +++ b/changelog.md @@ -23,7 +23,8 @@ - The undocumented ``#? strongSpaces`` parsing mode has been removed. - The `not` operator is now always a unary operator, this means that code like ``assert not isFalse(3)`` compiles. - +- `getImpl` on a `var` or `let` symbol will now return the full `IdentDefs` + tree from the symbol declaration instead of just the initializer portion. #### Breaking changes in the standard library @@ -133,8 +134,9 @@ proc enumToString*(enums: openArray[enum]): string = the `gcsafe` pragma block. - added os.getCurrentProcessId() - User defined pragmas are now allowed in the pragma blocks -- Pragma blocks are now longer eliminated from the typed AST tree to preserve +- Pragma blocks are no longer eliminated from the typed AST tree to preserve pragmas for further analysis by macros +- Custom pragmas are now supported for `var` and `let` symbols. ### Language changes @@ -143,10 +145,10 @@ proc enumToString*(enums: openArray[enum]): string = it's more recognizable and allows tools like github to recognize it as Nim, see [#9647](https://github.com/nim-lang/Nim/issues/9647). The previous extension will continue to work. -- Pragma syntax is now consistent. Previous syntax where type pragmas did not +- Pragma syntax is now consistent. Previous syntax where type pragmas did not follow the type name is now deprecated. Also pragma before generic parameter list is deprecated to be consistent with how pragmas are used with a proc. See - [#8514](https://github.com/nim-lang/Nim/issues/8514) and + [#8514](https://github.com/nim-lang/Nim/issues/8514) and [#1872](https://github.com/nim-lang/Nim/issues/1872) for further details. diff --git a/compiler/ast.nim b/compiler/ast.nim index 5f5f296cb..24891d6d3 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -1087,6 +1087,13 @@ proc newSym*(symKind: TSymKind, name: PIdent, owner: PSym, when debugIds: registerId(result) +proc astdef*(s: PSym): PNode = + # get only the definition (initializer) portion of the ast + if s.ast != nil and s.ast.kind == nkIdentDefs: + s.ast[2] + else: + s.ast + proc isMetaType*(t: PType): bool = return t.kind in tyMetaTypes or (t.kind == tyStatic and t.n == nil) or diff --git a/compiler/commands.nim b/compiler/commands.nim index 8b73884e8..47687046f 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -291,6 +291,7 @@ proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool of "patterns": result = contains(conf.options, optPatterns) of "excessivestacktrace": result = contains(conf.globalOptions, optExcessiveStackTrace) of "nilseqs": result = contains(conf.options, optNilSeqs) + of "oldast": result = contains(conf.options, optOldAst) else: invalidCmdLineOption(conf, passCmd1, switch, info) proc processPath(conf: ConfigRef; path: string, info: TLineInfo, @@ -508,6 +509,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; localError(conf, info, errOnOrOffExpectedButXFound % arg) of "laxstrings": processOnOffSwitch(conf, {optLaxStrings}, arg, pass, info) of "nilseqs": processOnOffSwitch(conf, {optNilSeqs}, arg, pass, info) + of "oldast": processOnOffSwitch(conf, {optOldAst}, arg, pass, info) of "checks", "x": processOnOffSwitch(conf, ChecksOptions, arg, pass, info) of "floatchecks": processOnOffSwitch(conf, {optNaNCheck, optInfCheck}, arg, pass, info) diff --git a/compiler/guards.nim b/compiler/guards.nim index a01c023e4..46e18d3bf 100644 --- a/compiler/guards.nim +++ b/compiler/guards.nim @@ -257,9 +257,9 @@ proc canon*(n: PNode; o: Operators): PNode = for i in 0 ..< n.len: result.sons[i] = canon(n.sons[i], o) elif n.kind == nkSym and n.sym.kind == skLet and - n.sym.ast.getMagic in (someEq + someAdd + someMul + someMin + + n.sym.astdef.getMagic in (someEq + someAdd + someMul + someMin + someMax + someHigh + {mUnaryLt} + someSub + someLen + someDiv): - result = n.sym.ast.copyTree + result = n.sym.astdef.copyTree else: result = n case result.getMagic @@ -395,8 +395,8 @@ proc usefulFact(n: PNode; o: Operators): PNode = # if a: # ... # We make can easily replace 'a' by '2 < x' here: - if n.sym.ast != nil: - result = usefulFact(n.sym.ast, o) + if n.sym.astdef != nil: + result = usefulFact(n.sym.astdef, o) elif n.kind == nkStmtListExpr: result = usefulFact(n.lastSon, o) diff --git a/compiler/options.nim b/compiler/options.nim index 3730d16f8..ea159ee1d 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -40,7 +40,8 @@ type # please make sure we have under 32 options optMemTracker, optHotCodeReloading, optLaxStrings, - optNilSeqs + optNilSeqs, + optOldAst TOptions* = set[TOption] TGlobalOption* = enum # **keep binary compatible** diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 39f200ba8..12283e042 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -330,9 +330,9 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym = proc checkNilable(c: PContext; v: PSym) = if {sfGlobal, sfImportC} * v.flags == {sfGlobal} and {tfNotNil, tfNeedsInit} * v.typ.flags != {}: - if v.ast.isNil: + if v.astdef.isNil: message(c.config, v.info, warnProveInit, v.name.s) - elif tfNotNil in v.typ.flags and tfNotNil notin v.ast.typ.flags: + elif tfNotNil in v.typ.flags and tfNotNil notin v.astdef.typ.flags: message(c.config, v.info, warnProveInit, v.name.s) include semasgn @@ -518,8 +518,6 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = message(c.config, a.info, warnShadowIdent, v.name.s) if a.kind != nkVarTuple: if def.kind != nkEmpty: - # this is needed for the evaluation pass and for the guard checking: - v.ast = def if sfThread in v.flags: localError(c.config, def.info, errThreadvarCannotInit) setVarType(c, v, typ) b = newNodeI(nkIdentDefs, a.info) @@ -531,6 +529,23 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = addSon(b, a.sons[length-2]) addSon(b, copyTree(def)) addToVarSection(c, result, n, b) + if optOldAst in c.config.options: + if def.kind != nkEmpty: + v.ast = def + else: + # this is needed for the evaluation pass, guard checking + # and custom pragmas: + var ast = newNodeI(nkIdentDefs, a.info) + if a[j].kind == nkPragmaExpr: + var p = newNodeI(nkPragmaExpr, a.info) + p.add newSymNode(v) + p.add a[j][1].copyTree + ast.add p + else: + ast.add newSymNode(v) + ast.add a.sons[length-2].copyTree + ast.add def + v.ast = ast else: if def.kind in {nkPar, nkTupleConstr}: v.ast = def[j] # bug #7663, for 'nim check' this can be a non-tuple: diff --git a/doc/advopt.txt b/doc/advopt.txt index 5ddd064ef..2e91deed9 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -76,6 +76,7 @@ Advanced options: strings is allowed; only for backwards compatibility --nilseqs:on|off allow 'nil' for strings/seqs for backwards compatibility + --oldast:on|off use old AST for backwards compatibility --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/core/macros.nim b/lib/core/macros.nim index 291858bed..64334161e 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -1402,8 +1402,14 @@ proc customPragmaNode(n: NimNode): NimNode = let impl = n.getImpl() if impl.kind in RoutineNodes: return impl.pragma + elif impl.kind == nnkIdentDefs and impl[0].kind == nnkPragmaExpr: + return impl[0][1] else: - return typ.getImpl()[0][1] + let timpl = typ.getImpl() + if timpl.len>0 and timpl[0].len>1: + return timpl[0][1] + else: + return timpl if n.kind in {nnkDotExpr, nnkCheckedFieldExpr}: let name = $(if n.kind == nnkCheckedFieldExpr: n[0][1] else: n[1]) diff --git a/tests/pragmas/tcustom_pragma.nim b/tests/pragmas/tcustom_pragma.nim index 0bc4d2f18..fefcc0b5f 100644 --- a/tests/pragmas/tcustom_pragma.nim +++ b/tests/pragmas/tcustom_pragma.nim @@ -175,24 +175,47 @@ var foo: Something foo.cardinal = north doAssert foo.b.hasCustomPragma(thingy) == true - -proc myproc(s: string): int = +proc myproc(s: string): int = {.thingy.}: s.len doAssert myproc("123") == 3 let xx = compiles: - proc myproc_bad(s: string): int = + proc myproc_bad(s: string): int = {.not_exist.}: s.len doAssert: xx == false - -macro checkSym(s: typed{nkSym}): untyped = +macro checkSym(s: typed{nkSym}): untyped = let body = s.getImpl.body doAssert body[1].kind == nnkPragmaBlock doAssert body[1][0].kind == nnkPragma doAssert body[1][0][0] == bindSym"thingy" -checkSym(myproc) \ No newline at end of file +checkSym(myproc) + +# var and let pragmas +block: + template myAttr() {.pragma.} + template myAttr2(x: int) {.pragma.} + template myAttr3(x: string) {.pragma.} + + let a {.myAttr,myAttr2(2),myAttr3:"test".}: int = 0 + let b {.myAttr,myAttr2(2),myAttr3:"test".} = 0 + var x {.myAttr,myAttr2(2),myAttr3:"test".}: int = 0 + var y {.myAttr,myAttr2(2),myAttr3:"test".}: int + var z {.myAttr,myAttr2(2),myAttr3:"test".} = 0 + + template check(s: untyped) = + doAssert s.hasCustomPragma(myAttr) + doAssert s.hasCustomPragma(myAttr2) + doAssert s.getCustomPragmaVal(myAttr2) == 2 + doAssert s.hasCustomPragma(myAttr3) + doAssert s.getCustomPragmaVal(myAttr3) == "test" + + check(a) + check(b) + check(x) + check(y) + check(z) -- cgit 1.4.1-2-gfad0 From 387831d657bcbd130b7e739e4d58f7ccc29adae9 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Mon, 7 Jan 2019 20:32:47 +0530 Subject: Show error when trying to export individual enum field (#10109) --- compiler/semexprs.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index f03af3db9..8852cb653 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -2335,7 +2335,6 @@ proc semExportExcept(c: PContext, n: PNode): PNode = proc semExport(c: PContext, n: PNode): PNode = result = newNodeI(nkExportStmt, n.info) - for i in 0.. Date: Mon, 7 Jan 2019 18:09:57 +0100 Subject: Fix for sizeof bitsize combination (#10227) * fix #10082 * added test --- compiler/sizealignoffsetimpl.nim | 11 ++++++++--- tests/misc/tsizeof.nim | 12 ++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/compiler/sizealignoffsetimpl.nim b/compiler/sizealignoffsetimpl.nim index ea4730f57..fb256b895 100644 --- a/compiler/sizealignoffsetimpl.nim +++ b/compiler/sizealignoffsetimpl.nim @@ -154,12 +154,17 @@ proc computePackedObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, initialOf if result == szIllegalRecursion: break of nkSym: + var size = szUnknownSize if n.sym.bitsize == 0: computeSizeAlign(conf, n.sym.typ) - n.sym.offset = initialOffset.int - result = n.sym.offset + n.sym.typ.size - else: + size = n.sym.typ.size.int + + if initialOffset == szUnknownSize or size == szUnknownSize: + n.sym.offset = szUnknownSize result = szUnknownSize + else: + n.sym.offset = int(initialOffset) + result = initialOffset + n.sym.typ.size else: result = szUnknownSize diff --git a/tests/misc/tsizeof.nim b/tests/misc/tsizeof.nim index 4422e900e..25c566171 100644 --- a/tests/misc/tsizeof.nim +++ b/tests/misc/tsizeof.nim @@ -402,6 +402,18 @@ type assert sizeof(C) == 3 + +type + MixedBitsize = object {.packed.} + a: uint32 + b {.bitsize: 8.}: uint8 + c {.bitsize: 1.}: uint8 + d {.bitsize: 7.}: uint8 + e {.bitsize: 16.}: uint16 + f: uint32 + +doAssert sizeof(MixedBitsize) == 12 + if failed: quit("FAIL") else: -- cgit 1.4.1-2-gfad0 From 20402579a2d22f506bf6079976c3480a7bd06531 Mon Sep 17 00:00:00 2001 From: alaviss Date: Tue, 8 Jan 2019 14:42:10 +0700 Subject: system/strmantle: avoid leaking docs to system.nim [ci skip] (#10233) thanks @timotheecour for spotting this --- lib/system/strmantle.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/system/strmantle.nim b/lib/system/strmantle.nim index ceaecb4f9..3c681fc53 100644 --- a/lib/system/strmantle.nim +++ b/lib/system/strmantle.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -## Compilerprocs for strings that do not depend on the string implementation. +# Compilerprocs for strings that do not depend on the string implementation. proc cmpStrings(a, b: string): int {.inline, compilerProc.} = let alen = a.len -- cgit 1.4.1-2-gfad0 From 3ed833198b95e74de29d2b0905247ead52478aa3 Mon Sep 17 00:00:00 2001 From: treeform Date: Tue, 8 Jan 2019 00:50:25 -0800 Subject: Better LibSSL search order. (#10230) --- lib/wrappers/openssl.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim index 2f072d5c7..68f0d8eb2 100644 --- a/lib/wrappers/openssl.nim +++ b/lib/wrappers/openssl.nim @@ -38,7 +38,7 @@ when useWinVersion: from winlean import SocketHandle else: - const versions = "(.1.1|.38|.39|.41|.43|.44|.45|.46|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|)" + const versions = "(.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|.1.1|.46|.45|.44|.43|.41|.39|.38|.10|)" when defined(macosx): const -- cgit 1.4.1-2-gfad0 From 427435a814248b8b5778aa0d78f128a3f60978cd Mon Sep 17 00:00:00 2001 From: Clyybber Date: Tue, 8 Jan 2019 10:13:39 +0100 Subject: Don't use deprecated pragma syntax (#10187) * Don't use deprecated pragma syntax * Remove pure pragma, since it's a noop now --- compiler/types.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/types.nim b/compiler/types.nim index b163ca4e9..63a87f14e 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -756,7 +756,7 @@ type TTypeCmpFlags* = set[TTypeCmpFlag] - TSameTypeClosure = object {.pure.} + TSameTypeClosure = object cmp: TDistinctCompare recCheck: int flags: TTypeCmpFlags -- cgit 1.4.1-2-gfad0 From 821920aa391c1361ee17befedf4452edba47c87c Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 8 Jan 2019 01:38:34 -0800 Subject: [nimpretty] fix #10211; fix #10199 (#10212) * [nimpretty] fix #10211; fix #10199 * address comments * un-document --backup and set its default to false --- changelog.md | 2 ++ nimpretty/nimpretty.nim | 24 ++++++++++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index 05f65516d..35d2adbd1 100644 --- a/changelog.md +++ b/changelog.md @@ -156,6 +156,8 @@ proc enumToString*(enums: openArray[enum]): string = - `jsondoc` now include a `moduleDescription` field with the module description. `jsondoc0` shows comments as it's own objects as shown in the documentation. +- `nimpretty`: --backup now defaults to `off` instead of `on` and the flag was + un-documented; use `git` instead of relying on backup files. ### Compiler changes - The deprecated `fmod` proc is now unavailable on the VM'. diff --git a/nimpretty/nimpretty.nim b/nimpretty/nimpretty.nim index 628bc163e..c6c558f92 100644 --- a/nimpretty/nimpretty.nim +++ b/nimpretty/nimpretty.nim @@ -25,11 +25,10 @@ const Usage: nimpretty [options] file.nim Options: - --backup:on|off create a backup file before overwritting (default: ON) - --output:file set the output file (default: overwrite the .nim file) - --indent:N set the number of spaces that is used for indentation - --version show the version - --help show this help + --output:file set the output file (default: overwrite the input file) + --indent:N[=2] set the number of spaces that is used for indentation + --version show the version + --help show this help """ proc writeHelp() = @@ -62,7 +61,11 @@ proc prettyPrint(infile, outfile: string, opt: PrettyOptions) = proc main = var infile, outfile: string - var backup = true + var backup = false + # when `on`, create a backup file of input in case + # `prettyPrint` could over-write it (note that the backup may happen even + # if input is not actually over-written, when nimpretty is a noop). + # --backup was un-documented (rely on git instead). var opt: PrettyOptions for kind, key, val in getopt(): case kind @@ -79,9 +82,14 @@ proc main = of cmdEnd: assert(false) # cannot happen if infile.len == 0: quit "[Error] no input file." + if outfile.len == 0: + outfile = infile + if not existsFile(outfile) or not sameFile(infile, outfile): + backup = false # no backup needed since won't be over-written if backup: - os.copyFile(source=infile, dest=changeFileExt(infile, ".nim.backup")) - if outfile.len == 0: outfile = infile + let infileBackup = infile & ".backup" # works with .nim or .nims + echo "writing backup " & infile & " > " & infileBackup + os.copyFile(source = infile, dest = infileBackup) prettyPrint(infile, outfile, opt) main() -- cgit 1.4.1-2-gfad0 From 6737634d88a70a3d87774c9f51f2ac6d2bf4da4f Mon Sep 17 00:00:00 2001 From: alaviss Date: Tue, 8 Jan 2019 18:41:15 +0700 Subject: os.execShellCmd: fixes #10231 (#10232) Darwin has long deprecated the wait union, but their macros still assume it unless you define _POSIX_C_SOURCE. This trips up C++ compilers. This commit duplicates the behavior of WEXITSTATUS when _POSIX_C_SOURCE is defined. --- lib/pure/os.nim | 4 +++- tests/stdlib/t10231.nim | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/stdlib/t10231.nim diff --git a/lib/pure/os.nim b/lib/pure/os.nim index a218121ed..3a959d4e2 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1297,7 +1297,9 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", ## the process has finished. To execute a program without having a ## shell involved, use the `execProcess` proc of the `osproc` ## module. - when defined(posix): + when defined(macosx): + result = c_system(command) shr 8 + elif defined(posix): result = WEXITSTATUS(c_system(command)) else: result = c_system(command) diff --git a/tests/stdlib/t10231.nim b/tests/stdlib/t10231.nim new file mode 100644 index 000000000..5d1101aa4 --- /dev/null +++ b/tests/stdlib/t10231.nim @@ -0,0 +1,13 @@ +discard """ + target: cpp + action: run + exitcode: 0 +""" + +import os + +if paramCount() == 0: + # main process + doAssert execShellCmd(getAppFilename().quoteShell & " test") == 1 +else: + quit 1 -- cgit 1.4.1-2-gfad0 From c60916a2aff121fc0220f9452803a7bcfb6300d0 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 8 Jan 2019 04:08:19 -0800 Subject: [CI] fixes #10041 move bulk of `travis` and `appveyor` logic to koch.nim (#10183) --- .travis.yml | 21 +++------------------ appveyor.yml | 18 +----------------- koch.nim | 46 +++++++++++++++++++++++++++++++++++++++++++--- tools/kochdocs.nim | 6 ++++++ 4 files changed, 53 insertions(+), 38 deletions(-) diff --git a/.travis.yml b/.travis.yml index abd115701..32a40bcaf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,26 +40,11 @@ before_script: - sh build.sh - cd .. - export PATH=$(pwd)/bin${PATH:+:$PATH} + - echo PATH:${PATH} + script: - nim c koch - - env NIM_COMPILE_TO_CPP=false ./koch boot - - ./koch boot -d:release - - ./koch nimble - - nim e tests/test_nimscript.nims - #- nimble install zip -y - #- nimble install opengl - #- nimble install sdl1 - #- nimble install jester@#head -y - #- nimble install niminst - - nim c -d:nimCoroutines testament/tester - - testament/tester --pedantic all -d:nimCoroutines - - nim c -o:bin/nimpretty nimpretty/nimpretty.nim - - nim c -r nimpretty/tester.nim - - ./koch docs --git.commit:devel - - ./koch csource - - ./koch nimsuggest - - nim c -r nimsuggest/tester - - nim c -r nimdoc/tester + - ./koch runCI before_deploy: # Make https://nim-lang.github.io/Nim work the same as https://nim-lang.github.io/Nim/overview.html diff --git a/appveyor.yml b/appveyor.yml index 9c8525d72..8c4b2a07e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -44,22 +44,6 @@ install: build_script: - bin\nim c koch - - koch boot -d:release - - koch nimble - - nim e tests/test_nimscript.nims - - nim c -o:bin/nimpretty.exe nimpretty/nimpretty.nim - - nim c -r nimpretty/tester.nim -# - nimble install zip -y -# - nimble install opengl -y -# - nimble install sdl1 -y -# - nimble install jester@#head -y - - nim c -d:nimCoroutines --os:genode -d:posix --compileOnly testament/tester - - nim c -d:nimCoroutines testament/tester - -test_script: - - testament\tester --pedantic all -d:nimCoroutines - - nim c -r nimdoc\tester -# - koch csource -# - koch zip + - koch runCI deploy: off diff --git a/koch.nim b/koch.nim index 3082dba8f..afdce3083 100644 --- a/koch.nim +++ b/koch.nim @@ -26,6 +26,7 @@ import import tools / kochdocs const VersionAsString = system.NimVersion +const env_NIM_COMPILE_TO_CPP = "NIM_COMPILE_TO_CPP" const HelpText = """ @@ -56,6 +57,7 @@ Boot options: for bootstrapping Commands for core developers: + runCI runs continuous integration (CI), eg from travis docs [options] generates the full documentation csource -d:release builds the C sources for installation pdf builds the PDF documentation @@ -272,8 +274,9 @@ proc boot(args: string) = var output = "compiler" / "nim".exe var finalDest = "bin" / "nim".exe # default to use the 'c' command: - let defaultCommand = if getEnv("NIM_COMPILE_TO_CPP", "false") == "true": "cpp" else: "c" + let defaultCommand = if getEnv(env_NIM_COMPILE_TO_CPP, "false") == "true": "cpp" else: "c" let bootOptions = if args.len == 0 or args.startsWith("-"): defaultCommand else: "" + echo "boot: defaultCommand: ", defaultCommand, " bootOptions: ", bootOptions let smartNimcache = (if "release" in args: "nimcache/r_" else: "nimcache/d_") & hostOs & "_" & hostCpu @@ -352,8 +355,8 @@ proc winReleaseArch(arch: string) = # Rebuilding koch is necessary because it uses its pointer size to # determine which mingw link to put in the NSIS installer. nimexec "c --cpu:$# koch" % cpu - exec "koch boot -d:release --cpu:$#" % cpu - exec "koch --latest zip -d:release" + kochExec "boot -d:release --cpu:$#" % cpu + kochExec "--latest zip -d:release" overwriteFile r"build\nim-$#.zip" % VersionAsString, r"web\upload\download\nim-$#_x$#.zip" % [VersionAsString, arch] @@ -428,6 +431,42 @@ proc xtemp(cmd: string) = finally: copyExe(d / "bin" / "nim_backup".exe, d / "bin" / "nim".exe) +proc runCI(cmd: string) = + doAssert cmd.len == 0, cmd # avoid silently ignoring + echo "runCI:", cmd + # note(@araq): Do not replace these commands with direct calls (eg boot()) + # as that would weaken our testing efforts. + when defined(posix): # appveyor (on windows) didn't run this + # todo: implement `execWithEnv` + exec("env NIM_COMPILE_TO_CPP=false $1 boot" % kochExe.quoteShell) + kochExec "boot -d:release" + kochExec "nimble" + exec "nim e tests/test_nimscript.nims" + + when false: + for pkg in "zip opengl sdl1 jester@#head niminst".split: + exec "nimble install -y" & pkg + + when defined(windows): + # note: will be over-written below + exec "nim c -d:nimCoroutines --os:genode -d:posix --compileOnly testament/tester" + when false: + kochExec "csource" + kochExec "zip" + + # main bottleneck: runs all main tests + exec "nim c -r -d:nimCoroutines testament/tester --pedantic all -d:nimCoroutines" + exec "nim c -r nimdoc/tester" + + nimCompile "nimpretty/nimpretty.nim" + exec "nim c -r nimpretty/tester.nim" + + when defined(posix): + kochExec "docs --git.commit:devel" + kochExec "csource" + kochExec "nimsuggest" + exec "nim c -r nimsuggest/tester" + proc pushCsources() = if not dirExists("../csources/.git"): quit "[Error] no csources git repository found" @@ -536,6 +575,7 @@ when isMainModule: of "distrohelper": geninstall() of "install": install(op.cmdLineRest) of "testinstall": testUnixInstall(op.cmdLineRest) + of "runci": runCI(op.cmdLineRest) of "test", "tests": tests(op.cmdLineRest) of "temp": temp(op.cmdLineRest) of "xtemp": xtemp(op.cmdLineRest) diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim index 6741fcf5d..19b0b3d78 100644 --- a/tools/kochdocs.nim +++ b/tools/kochdocs.nim @@ -51,6 +51,12 @@ proc execCleanPath*(cmd: string, if execShellCmd(cmd) != 0: quit("FAILURE", errorcode) putEnv("PATH", prevPath) +let kochExe* = os.getAppFilename() + # note: assumes `kochdocs` is only used by koch.nim + +proc kochExec*(cmd: string) = + exec kochExe.quoteShell & " " & cmd + proc nimexec*(cmd: string) = # Consider using `nimCompile` instead exec findNim() & " " & cmd -- cgit 1.4.1-2-gfad0 From d0366c519487610ff26d28175e98a9c3ef178670 Mon Sep 17 00:00:00 2001 From: narimiran Date: Sat, 5 Jan 2019 10:12:13 +0100 Subject: update issue templates [ci skip] --- .github/ISSUE_TEMPLATE/bug_report.md | 24 ++++++++++++------------ .github/ISSUE_TEMPLATE/feature_request.md | 17 +++++++++++++---- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 383ec23f8..5fe7971a3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,37 +1,32 @@ --- name: Bug report -about: You found an unexpected behaviour? Use this template. - +about: Have you found an unexpected behavior? Use this template. --- - - + +Function `echo` outputs the wrong string. ### Example - ```nim echo "Hello World!" ``` ---> ### Current Output - ### Expected Output - ``` Hello World! ``` ---> ### Possible Solution @@ -40,6 +35,11 @@ Hello World! ### Additional Information +``` +$ nim -v +Nim Compiler Version 0.1.2 +``` diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 04da773bf..52aa53a67 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,9 +1,18 @@ --- name: Feature request -about: You want to suggest a new feature? Use this template. +about: Do you want to suggest a new feature? Use this template. --- + + + ### Summary @@ -18,6 +27,6 @@ about: You want to suggest a new feature? Use this template. ### Additional Information +* A link to a project where the issue is relevant. +* A link to a related issue or discussion. +--> -- cgit 1.4.1-2-gfad0 From 21078798ead6f6ba107102913cd26970ef957e9d Mon Sep 17 00:00:00 2001 From: Andre von Houck Date: Tue, 8 Jan 2019 10:29:10 -0800 Subject: Fix libssl order. Newest one is 1.1 --- lib/wrappers/openssl.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim index 68f0d8eb2..447592a68 100644 --- a/lib/wrappers/openssl.nim +++ b/lib/wrappers/openssl.nim @@ -38,7 +38,7 @@ when useWinVersion: from winlean import SocketHandle else: - const versions = "(.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|.1.1|.46|.45|.44|.43|.41|.39|.38|.10|)" + const versions = "(.1.1|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|.46|.45|.44|.43|.41|.39|.38|.10|)" when defined(macosx): const -- cgit 1.4.1-2-gfad0 From fb26b95f815b5426e0a8aad98ca0ff018ef1f4db Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Wed, 9 Jan 2019 00:14:47 +0530 Subject: {.deprecated: msg.} now works for vars and lets (#10234) --- compiler/pragmas.nim | 2 +- compiler/suggest.nim | 8 ++++++-- tests/deprecated/tannot.nim | 9 +++++++++ tests/deprecated/tnoannot.nim | 7 ------- 4 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 tests/deprecated/tannot.nim delete mode 100644 tests/deprecated/tnoannot.nim diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 39b58d0b1..bb5707cd5 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -881,7 +881,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, of wExplain: sym.flags.incl sfExplain of wDeprecated: - if sym != nil and sym.kind in routineKinds + {skType}: + if sym != nil and sym.kind in routineKinds + {skType, skVar, skLet}: if it.kind in nkPragmaCallKinds: discard getStrLitNode(c, it) incl(sym.flags, sfDeprecated) elif sym != nil and sym.kind != skModule: diff --git a/compiler/suggest.nim b/compiler/suggest.nim index 144b86224..f3f960136 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -456,13 +456,17 @@ proc suggestSym*(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym; proc extractPragma(s: PSym): PNode = if s.kind in routineKinds: result = s.ast[pragmasPos] - elif s.kind in {skType}: + elif s.kind in {skType, skVar, skLet}: # s.ast = nkTypedef / nkPragmaExpr / [nkSym, nkPragma] result = s.ast[0][1] doAssert result == nil or result.kind == nkPragma proc warnAboutDeprecated(conf: ConfigRef; info: TLineInfo; s: PSym) = - let pragmaNode = if s.kind == skEnumField: extractPragma(s.owner) else: extractPragma(s) + var pragmaNode: PNode + if optOldAst in conf.options and s.kind in {skVar, skLet}: + pragmaNode = nil + else: + pragmaNode = if s.kind == skEnumField: extractPragma(s.owner) else: extractPragma(s) let name = if s.kind == skEnumField and sfDeprecated notin s.flags: "enum '" & s.owner.name.s & "' which contains field '" & s.name.s & "'" else: s.name.s diff --git a/tests/deprecated/tannot.nim b/tests/deprecated/tannot.nim new file mode 100644 index 000000000..d14f6cc23 --- /dev/null +++ b/tests/deprecated/tannot.nim @@ -0,0 +1,9 @@ +discard """ + nimout: '''tannot.nim(9, 1) Warning: efgh; foo1 is deprecated [Deprecated] +tannot.nim(9, 8) Warning: abcd; foo is deprecated [Deprecated] +''' +""" + +let foo* {.deprecated: "abcd".} = 42 +var foo1* {.deprecated: "efgh".} = 42 +foo1 = foo diff --git a/tests/deprecated/tnoannot.nim b/tests/deprecated/tnoannot.nim deleted file mode 100644 index ac168952e..000000000 --- a/tests/deprecated/tnoannot.nim +++ /dev/null @@ -1,7 +0,0 @@ -discard """ - errormsg: "annotation to deprecated not supported here" - line: 7 -""" - -var foo* {.deprecated.} = 42 -var foo1* {.deprecated: "no".} = 42 -- cgit 1.4.1-2-gfad0 From bf3a308e86e7c5999855546962aed564218a8121 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 8 Jan 2019 15:58:47 -0800 Subject: [error messages, stacktraces] fix #8794 #9270 #9767 #9768 (#9766) * fixes #8794 : `Error: undeclared field: 'foo'` should show type (+ where type is defined) (hard to guess in generic code) * fixes #9270: `--listFullPaths` not honored by `declared in foo.nim` messages * fixes #9767: VM stacktrace doesn't honor --excessiveStackTrace:on * fixes #9768: VM stacktrace misses column info, can lead to ambiguous or harder to read stacktraces * refactors some col+1 code to col + ColOffset (self documents code) * make getProcHeader show declared info location also for types and all routine kinds (including macros,templates) instead of just (rather arbitrarily) for iterator,proc,func,method * --listFullPaths now is honored in more places * fix typo system/except.nim => lib/system/excpt.nim * remove substr(foo, 0) hack in compiler/vm.nim which seems old and not applicable anymore --- compiler/lookups.nim | 2 +- compiler/msgs.nim | 24 +++++++++++++++--------- compiler/options.nim | 2 +- compiler/semcall.nim | 15 ++++++++++++++- compiler/types.nim | 31 +++++++++++++++++-------------- compiler/vm.nim | 15 ++++++++++----- tests/errmsgs/m8794.nim | 2 ++ tests/errmsgs/t8794.nim | 39 +++++++++++++++++++++++++++++++++++++++ tests/errmsgs/t9768.nim | 30 ++++++++++++++++++++++++++++++ 9 files changed, 129 insertions(+), 31 deletions(-) create mode 100644 tests/errmsgs/m8794.nim create mode 100644 tests/errmsgs/t8794.nim create mode 100644 tests/errmsgs/t9768.nim diff --git a/compiler/lookups.nim b/compiler/lookups.nim index d4959db12..269447486 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -150,7 +150,7 @@ type proc getSymRepr*(conf: ConfigRef; s: PSym): string = case s.kind - of skProc, skFunc, skMethod, skConverter, skIterator: + of routineKinds, skType: result = getProcHeader(conf, s) else: result = s.name.s diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 18c196085..0dd5820b4 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -135,6 +135,10 @@ const WarningColor = fgYellow HintTitle = "Hint: " HintColor = fgGreen + # NOTE: currently line info line numbers start with 1, + # but column numbers start with 0, however most editors expect + # first column to be 1, so we need to +1 here + ColOffset* = 1 proc getInfoContextLen*(conf: ConfigRef): int = return conf.m.msgContext.len proc setInfoContextLen*(conf: ConfigRef; L: int) = setLen(conf.m.msgContext, L) @@ -155,7 +159,10 @@ template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string = if fileIdx.int32 < 0 or conf == nil: "???" else: - conf.m.fileInfos[fileIdx.int32].projPath.string + if optListFullPaths in conf.globalOptions: + conf.m.fileInfos[fileIdx.int32].fullPath.string + else: + conf.m.fileInfos[fileIdx.int32].projPath.string proc toFullPath*(conf: ConfigRef; fileIdx: FileIndex): string = if fileIdx.int32 < 0 or conf == nil: result = "???" @@ -192,10 +199,10 @@ proc toMsgFilename*(conf: ConfigRef; info: TLineInfo): string = result = "???" return let absPath = conf.m.fileInfos[info.fileIndex.int32].fullPath.string - let relPath = conf.m.fileInfos[info.fileIndex.int32].projPath.string if optListFullPaths in conf.globalOptions: result = absPath else: + let relPath = conf.m.fileInfos[info.fileIndex.int32].projPath.string result = if absPath.len < relPath.len: absPath else: relPath proc toLinenumber*(info: TLineInfo): int {.inline.} = @@ -208,7 +215,9 @@ proc toFileLine*(conf: ConfigRef; info: TLineInfo): string {.inline.} = result = toFilename(conf, info) & ":" & $info.line proc toFileLineCol*(conf: ConfigRef; info: TLineInfo): string {.inline.} = - result = toFilename(conf, info) & "(" & $info.line & ", " & $(info.col+1) & ")" + # consider calling `helpers.lineInfoToString` instead + result = toFilename(conf, info) & "(" & $info.line & ", " & + $(info.col + ColOffset) & ")" proc `$`*(conf: ConfigRef; info: TLineInfo): string = toFileLineCol(conf, info) @@ -359,7 +368,7 @@ proc writeContext(conf: ConfigRef; lastinfo: TLineInfo) = styledMsgWriteln(styleBright, PosFormat % [toMsgFilename(conf, context.info), coordToStr(context.info.line.int), - coordToStr(context.info.col+1)], + coordToStr(context.info.col+ColOffset)], resetStyle, message) info = context.info @@ -446,7 +455,7 @@ proc formatMsg*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string): s of hintMin..hintMax: HintTitle else: ErrorTitle result = PosFormat % [toMsgFilename(conf, info), coordToStr(info.line.int), - coordToStr(info.col+1)] & + coordToStr(info.col+ColOffset)] & title & getMessageStr(msg, arg) @@ -483,11 +492,8 @@ proc liMessage(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string, color = HintColor if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)] inc(conf.hintCounter) - # NOTE: currently line info line numbers start with 1, - # but column numbers start with 0, however most editors expect - # first column to be 1, so we need to +1 here let x = PosFormat % [toMsgFilename(conf, info), coordToStr(info.line.int), - coordToStr(info.col+1)] + coordToStr(info.col+ColOffset)] let s = getMessageStr(msg, arg) if not ignoreMsg: diff --git a/compiler/options.nim b/compiler/options.nim index ea159ee1d..d39b0a268 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -78,7 +78,7 @@ type # please make sure we have under 32 options optShowAllMismatches # show all overloading resolution candidates optWholeProject # for 'doc2': output any dependency optMixedMode # true if some module triggered C++ codegen - optListFullPaths + optListFullPaths # use full paths in toMsgFilename, toFilename optNoNimblePath optDynlibOverrideAll diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 7e0ea5490..7991640ea 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -287,7 +287,20 @@ proc getMsgDiagnostic(c: PContext, flags: TExprFlags, n, f: PNode): string = let ident = considerQuotedIdent(c, f, n).s if nfDotField in n.flags and nfExplicitCall notin n.flags: - result = errUndeclaredField % ident & result + let sym = n.sons[1].typ.sym + var typeHint = "" + if sym == nil: + #[ + Perhaps we're in a `compiles(foo.bar)` expression, or + in a concept, eg: + ExplainedConcept {.explain.} = concept o + o.foo is int + We coudl use: `(c.config $ n.sons[1].info)` to get more context. + ]# + discard + else: + typeHint = " for type " & getProcHeader(c.config, sym) + result = errUndeclaredField % ident & typeHint & " " & result else: if result.len == 0: result = errUndeclaredRoutine % ident else: result = errBadRoutine % [ident, result] diff --git a/compiler/types.nim b/compiler/types.nim index 63a87f14e..5b8b9e602 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -107,20 +107,23 @@ proc isFloatLit*(t: PType): bool {.inline.} = result = t.kind == tyFloat and t.n != nil and t.n.kind == nkFloatLit proc getProcHeader*(conf: ConfigRef; sym: PSym; prefer: TPreferedDesc = preferName): string = - result = sym.owner.name.s & '.' & sym.name.s & '(' - var n = sym.typ.n - for i in countup(1, sonsLen(n) - 1): - let p = n.sons[i] - if p.kind == nkSym: - add(result, p.sym.name.s) - add(result, ": ") - add(result, typeToString(p.sym.typ, prefer)) - if i != sonsLen(n)-1: add(result, ", ") - else: - result.add renderTree(p) - add(result, ')') - if n.sons[0].typ != nil: - result.add(": " & typeToString(n.sons[0].typ, prefer)) + assert sym != nil + result = sym.owner.name.s & '.' & sym.name.s + if sym.kind in routineKinds: + result.add '(' + var n = sym.typ.n + for i in countup(1, sonsLen(n) - 1): + let p = n.sons[i] + if p.kind == nkSym: + add(result, p.sym.name.s) + add(result, ": ") + add(result, typeToString(p.sym.typ, prefer)) + if i != sonsLen(n)-1: add(result, ", ") + else: + result.add renderTree(p) + add(result, ')') + if n.sons[0].typ != nil: + result.add(": " & typeToString(n.sons[0].typ, prefer)) result.add "[declared in " result.add(conf$sym.info) result.add "]" diff --git a/compiler/vm.nim b/compiler/vm.nim index c8784c3e7..e2586e615 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -65,15 +65,20 @@ proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int; recursionLimit=100) = return stackTraceAux(c, x.next, x.comesFrom, recursionLimit-1) var info = c.debug[pc] - # we now use the same format as in system/except.nim - var s = substr(toFilename(c.config, info), 0) - # this 'substr' prevents a strange corruption. XXX This needs to be - # investigated eventually but first attempts to fix it broke everything - # see the araq-wip-fixed-writebarrier branch. + # we now use a format similar to the one in lib/system/excpt.nim + var s = "" + # todo: factor with quotedFilename + if optExcessiveStackTrace in c.config.globalOptions: + s = toFullPath(c.config, info) + else: + s = toFilename(c.config, info) var line = toLinenumber(info) + var col = toColumn(info) if line > 0: add(s, '(') add(s, $line) + add(s, ", ") + add(s, $(col + ColOffset)) add(s, ')') if x.prc != nil: for k in 1..max(1, 25-s.len): add(s, ' ') diff --git a/tests/errmsgs/m8794.nim b/tests/errmsgs/m8794.nim new file mode 100644 index 000000000..12e61cf54 --- /dev/null +++ b/tests/errmsgs/m8794.nim @@ -0,0 +1,2 @@ +type Foo3* = object + a1: int diff --git a/tests/errmsgs/t8794.nim b/tests/errmsgs/t8794.nim new file mode 100644 index 000000000..7f16a42fe --- /dev/null +++ b/tests/errmsgs/t8794.nim @@ -0,0 +1,39 @@ +discard """ + cmd: "nim check $options $file" + errormsg: "" + nimout: ''' +t8794.nim(39, 27) Error: undeclared field: 'a3' for type m8794.Foo3[declared in m8794.nim(1, 6)] +''' +""" + + + + + + + + + + + + +## line 20 + +## issue #8794 + +import m8794 + +when false: # pending https://github.com/nim-lang/Nim/pull/10091 add this + type Foo = object + a1: int + + discard Foo().a2 + +type Foo3b = Foo3 +var x2: Foo3b + +proc getFun[T](): T = + var a: T + a + +discard getFun[type(x2)]().a3 diff --git a/tests/errmsgs/t9768.nim b/tests/errmsgs/t9768.nim new file mode 100644 index 000000000..18588c87c --- /dev/null +++ b/tests/errmsgs/t9768.nim @@ -0,0 +1,30 @@ +discard """ + errmsg: "unhandled exception:" + file: "system.nim" + nimout: ''' +stack trace: (most recent call last) +t9768.nim(28, 33) main +t9768.nim(23, 11) foo1 +''' +""" + + + + + + + + + + +## line 20 + +proc foo1(a: int): auto = + doAssert a < 4 + result = a * 2 + +proc main()= + static: + if foo1(1) > 0: discard foo1(foo1(2)) + +main() -- cgit 1.4.1-2-gfad0 From fc7fcca9df305c02489bad4b969621cf9d8cb4f5 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 8 Jan 2019 17:07:46 -0800 Subject: fix leftover comment from #9766 --- compiler/semcall.nim | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 7991640ea..3723d3fc1 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -290,13 +290,11 @@ proc getMsgDiagnostic(c: PContext, flags: TExprFlags, n, f: PNode): string = let sym = n.sons[1].typ.sym var typeHint = "" if sym == nil: - #[ - Perhaps we're in a `compiles(foo.bar)` expression, or - in a concept, eg: - ExplainedConcept {.explain.} = concept o - o.foo is int - We coudl use: `(c.config $ n.sons[1].info)` to get more context. - ]# + # Perhaps we're in a `compiles(foo.bar)` expression, or + # in a concept, eg: + # ExplainedConcept {.explain.} = concept x + # x.foo is int + # We coudl use: `(c.config $ n.sons[1].info)` to get more context. discard else: typeHint = " for type " & getProcHeader(c.config, sym) -- cgit 1.4.1-2-gfad0 From 6ce3949c8aa489d268f272829d03edab175bfc7a Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 8 Jan 2019 18:37:25 -0800 Subject: add `isNamedTuple`; make $(1, 2) be (1, 2) instead of (Field0: 1, Field1: 2) which leaked implementation detail (#10070) * add `isNamedTuple`; make $(1, 2) be (1, 2) instead of leaking implementation detail (Field0: 1, Field1: 2) fixes this: #8670 (comment) /cc @alehander42 @Vindaar @mratsim * Note: isNamedTuple is useful in other places, eg #10010 (comment) * move isNamedTuple to helpers.nim to avoid exposing new symbol to system.nim * remove workaround in tests/vm/tissues.nim failing test now that #10218 was makes it work --- lib/pure/typetraits.nim | 3 +++ lib/system.nim | 21 +++++++++++++----- lib/system/helpers.nim | 19 ++++++++++++++++- lib/system/helpers2.nim | 2 ++ tests/array/tarray.nim | 2 +- tests/ccgbugs/t5701.nim | 6 +++--- tests/collections/tcollections_to_string.nim | 6 +++--- tests/errmsgs/tnested_generic_instantiation.nim | 6 ++++++ tests/errmsgs/tnested_generic_instantiation2.nim | 27 ++++++++++++++++++++++++ tests/generics/treentranttypes.nim | 2 +- tests/metatype/tmetatype_issues.nim | 2 +- tests/metatype/ttypetraits2.nim | 15 +++++++++++++ tests/system/tsystem_misc.nim | 11 ++++++++++ tests/vm/tissues.nim | 14 ++++++------ 14 files changed, 113 insertions(+), 23 deletions(-) create mode 100644 tests/errmsgs/tnested_generic_instantiation2.nim diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim index 5f5bfdbd7..a373a9370 100644 --- a/lib/pure/typetraits.nim +++ b/lib/pure/typetraits.nim @@ -10,7 +10,10 @@ ## This module defines compile-time reflection procs for ## working with types +include "system/helpers" # for `isNamedTuple` + export system.`$` +export isNamedTuple proc name*(t: typedesc): string {.magic: "TypeTrait".} ## Alias for system.`$`(t) since Nim v0.20.0. diff --git a/lib/system.nim b/lib/system.nim index 1d2401940..c68eba2e0 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2669,19 +2669,28 @@ proc compiles*(x: untyped): bool {.magic: "Compiles", noSideEffect, compileTime. ## echo "'+' for integers is available" discard +include "system/helpers" # for `lineInfoToString`, `isNamedTuple` + proc `$`*[T: tuple|object](x: T): string = ## generic ``$`` operator for tuples that is lifted from the components ## of `x`. Example: ## ## .. code-block:: nim - ## $(23, 45) == "(Field0: 23, Field1: 45)" + ## $(23, 45) == "(23, 45)" + ## $(a: 23, b: 45) == "(a: 23, b: 45)" ## $() == "()" result = "(" var firstElement = true + const isNamed = T is object or isNamedTuple(T) + when not isNamed: + var count = 0 for name, value in fieldPairs(x): if not firstElement: result.add(", ") - result.add(name) - result.add(": ") + when isNamed: + result.add(name) + result.add(": ") + else: + count.inc when compiles($value): when value isnot string and value isnot seq and compiles(value.isNil): if value.isNil: result.add "nil" @@ -2691,6 +2700,10 @@ proc `$`*[T: tuple|object](x: T): string = firstElement = false else: result.add("...") + when not isNamed: + if count == 1: + result.add(",") # $(1,) should print as the semantically legal (1,) + result.add(")") proc collectionToString[T](x: T, prefix, separator, suffix: string): string = @@ -3958,8 +3971,6 @@ proc failedAssertImpl*(msg: string) {.raises: [], tags: [].} = tags: [].} Hide(raiseAssert)(msg) -include "system/helpers" # for `lineInfoToString` - template assertImpl(cond: bool, msg: string, expr: string, enabled: static[bool]) = const loc = $instantiationInfo(-1, true) bind instantiationInfo diff --git a/lib/system/helpers.nim b/lib/system/helpers.nim index 7b2b32679..a7e47915e 100644 --- a/lib/system/helpers.nim +++ b/lib/system/helpers.nim @@ -6,6 +6,23 @@ proc lineInfoToString(file: string, line, column: int): string = file & "(" & $line & ", " & $column & ")" -proc `$`(info: type(instantiationInfo(0))): string = +type InstantiationInfo = tuple[filename: string, line: int, column: int] + +proc `$`(info: InstantiationInfo): string = # The +1 is needed here + # instead of overriding `$` (and changing its meaning), consider explicit name. lineInfoToString(info.fileName, info.line, info.column+1) + +proc isNamedTuple(T: type): bool = + ## return true for named tuples, false for any other type. + when T isnot tuple: result = false + else: + var t: T + for name, _ in t.fieldPairs: + when name == "Field0": + return compiles(t.Field0) + else: + return true + # empty tuple should be un-named, + # see https://github.com/nim-lang/Nim/issues/8861#issue-356631191 + return false diff --git a/lib/system/helpers2.nim b/lib/system/helpers2.nim index 1c9e7c068..c67a2c278 100644 --- a/lib/system/helpers2.nim +++ b/lib/system/helpers2.nim @@ -1,3 +1,5 @@ +# imported by other modules, unlike helpers.nim which is included + template formatErrorIndexBound*[T](i, a, b: T): string = "index out of bounds: (a:" & $a & ") <= (i:" & $i & ") <= (b:" & $b & ") " diff --git a/tests/array/tarray.nim b/tests/array/tarray.nim index 2a371b788..f7c1dbf7f 100644 --- a/tests/array/tarray.nim +++ b/tests/array/tarray.nim @@ -18,7 +18,7 @@ paper @[2, 3, 4]321 9.0 4.0 3 -@[(Field0: 1, Field1: 2), (Field0: 3, Field1: 5)] +@[(1, 2), (3, 5)] 2 @["a", "new one", "c"] @[1, 2, 3] diff --git a/tests/ccgbugs/t5701.nim b/tests/ccgbugs/t5701.nim index e69acbf31..ee6e48498 100644 --- a/tests/ccgbugs/t5701.nim +++ b/tests/ccgbugs/t5701.nim @@ -1,7 +1,7 @@ discard """ - output: '''(Field0: 1, Field1: 1) -(Field0: 2, Field1: 2) -(Field0: 3, Field1: 3) + output: '''(1, 1) +(2, 2) +(3, 3) ''' """ diff --git a/tests/collections/tcollections_to_string.nim b/tests/collections/tcollections_to_string.nim index 0c4f1e91c..686b9916b 100644 --- a/tests/collections/tcollections_to_string.nim +++ b/tests/collections/tcollections_to_string.nim @@ -9,9 +9,9 @@ import lists import critbits # Tests for tuples -doAssert $(1, 2, 3) == "(Field0: 1, Field1: 2, Field2: 3)" -doAssert $("1", "2", "3") == """(Field0: "1", Field1: "2", Field2: "3")""" -doAssert $('1', '2', '3') == """(Field0: '1', Field1: '2', Field2: '3')""" +doAssert $(1, 2, 3) == "(1, 2, 3)" +doAssert $("1", "2", "3") == """("1", "2", "3")""" +doAssert $('1', '2', '3') == """('1', '2', '3')""" # Tests for seqs doAssert $(@[1, 2, 3]) == "@[1, 2, 3]" diff --git a/tests/errmsgs/tnested_generic_instantiation.nim b/tests/errmsgs/tnested_generic_instantiation.nim index 6aea7cbcc..77353605c 100644 --- a/tests/errmsgs/tnested_generic_instantiation.nim +++ b/tests/errmsgs/tnested_generic_instantiation.nim @@ -17,3 +17,9 @@ converter toWrapped[T](value: T): Wrapped[T] = let result = Plain() discard $result + +proc foo[T2](a: Wrapped[T2]) = + # Error: generic instantiation too nested + discard $a + +foo(result) diff --git a/tests/errmsgs/tnested_generic_instantiation2.nim b/tests/errmsgs/tnested_generic_instantiation2.nim new file mode 100644 index 000000000..d9bba15b0 --- /dev/null +++ b/tests/errmsgs/tnested_generic_instantiation2.nim @@ -0,0 +1,27 @@ +discard """ +errormsg: "generic instantiation too nested" +""" + +#[ +bug #4766 +see also: tnested_generic_instantiation.nim +]# + +proc toString*[T](x: T) = + for name, value in fieldPairs(x): + when compiles(toString(value)): + discard + toString(value) + +type + Plain = ref object + discard + + Wrapped[T] = object + value: T + +converter toWrapped[T](value: T): Wrapped[T] = + Wrapped[T](value: value) + +let result = Plain() +toString(result) diff --git a/tests/generics/treentranttypes.nim b/tests/generics/treentranttypes.nim index 2ef049ce2..31fa25293 100644 --- a/tests/generics/treentranttypes.nim +++ b/tests/generics/treentranttypes.nim @@ -1,6 +1,6 @@ discard """ output: ''' -(Field0: 10, Field1: (Field0: "test", Field1: 1.2)) +(10, ("test", 1.2)) 3x3 Matrix [[0.0, 2.0, 3.0], [2.0, 0.0, 5.0], [2.0, 0.0, 5.0]] 2x3 Matrix [[0.0, 2.0, 3.0], [2.0, 0.0, 5.0]] diff --git a/tests/metatype/tmetatype_issues.nim b/tests/metatype/tmetatype_issues.nim index c5040f9ba..08d486de2 100644 --- a/tests/metatype/tmetatype_issues.nim +++ b/tests/metatype/tmetatype_issues.nim @@ -1,7 +1,7 @@ discard """ output:''' void -(Field0: "string", Field1: "string") +("string", "string") 1 mod 7 @[2, 2, 2, 2, 2] impl 2 called diff --git a/tests/metatype/ttypetraits2.nim b/tests/metatype/ttypetraits2.nim index de80e10b1..a436da7ec 100644 --- a/tests/metatype/ttypetraits2.nim +++ b/tests/metatype/ttypetraits2.nim @@ -1,3 +1,18 @@ # todo: merge with $nimc_D/tests/metatype/ttypetraits.nim (currently disabled) from typetraits import `$` # checks fix for https://github.com/c-blake/cligen/issues/84 + +import typetraits + +block: # isNamedTuple + type Foo1 = (a:1,).type + type Foo2 = (Field0:1,).type + type Foo3 = ().type + type Foo4 = object + + doAssert (a:1,).type.isNamedTuple + doAssert Foo1.isNamedTuple + doAssert Foo2.isNamedTuple + doAssert not Foo3.isNamedTuple + doAssert not Foo4.isNamedTuple + doAssert not (1,).type.isNamedTuple diff --git a/tests/system/tsystem_misc.nim b/tests/system/tsystem_misc.nim index f53a86e9a..a20e6b3bf 100644 --- a/tests/system/tsystem_misc.nim +++ b/tests/system/tsystem_misc.nim @@ -148,3 +148,14 @@ let a = @[1, 2, 3] # a.boundedOpenArray(1, 2).foo() # Works echo a.boundedOpenArray(1, 2).len # Internal compiler error + +block: # `$`*[T: tuple|object](x: T) + doAssert $(foo1:0, bar1:"a") == """(foo1: 0, bar1: "a")""" + doAssert $(foo1:0, ) == """(foo1: 0)""" + doAssert $(0, "a") == """(0, "a")""" + doAssert $(0, ) == "(0,)" + type Foo = object + x:int + x2:float + doAssert $Foo(x:2) == "(x: 2, x2: 0.0)" + doAssert $() == "()" diff --git a/tests/vm/tissues.nim b/tests/vm/tissues.nim index 021b902ad..063559d2e 100644 --- a/tests/vm/tissues.nim +++ b/tests/vm/tissues.nim @@ -1,16 +1,14 @@ -discard """ - nimout: "(Field0: 2, Field1: 2, Field2: 2, Field3: 2)" -""" - import macros -block t9043: - proc foo[N: static[int]](dims: array[N, int])= +block t9043: # issue #9043 + proc foo[N: static[int]](dims: array[N, int]): string = const N1 = N const N2 = dims.len - static: echo (N, dims.len, N1, N2) + const ret = $(N, dims.len, N1, N2) + static: doAssert ret == $(N, dims.len, N1, N2) + ret - foo([1, 2]) + doAssert foo([1, 2]) == "(2, 2, 2, 2)" block t4952: proc doCheck(tree: NimNode) = -- cgit 1.4.1-2-gfad0 From 258952832f312a25d0ab9771237a547297b336b8 Mon Sep 17 00:00:00 2001 From: narimiran Date: Wed, 9 Jan 2019 07:01:32 +0100 Subject: remove float128 from the manual, fixes #10213 [ci skip] --- doc/manual.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/manual.rst b/doc/manual.rst index f79811002..09b9b4d78 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -480,7 +480,6 @@ The type suffixes are: ``'d`` float64 ``'f32`` float32 ``'f64`` float64 - ``'f128`` float128 ================= ========================= Floating point literals may also be in binary, octal or hexadecimal -- cgit 1.4.1-2-gfad0 From 23c1ee982e2d3795001879a4527581f33875cd33 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 9 Jan 2019 00:46:44 -0800 Subject: add `alignTable`, `parseTableCells` to align/format a tab(etc) delimited table (#10182) * add compiler/unittest_light.nim for easy diffing: assertEquals and mismatch * fixup * add alignTable, parseTableCells --- compiler/asciitables.nim | 83 ++++++++++++++++++++++++++++ compiler/unittest_light.nim | 37 +++++++++++++ tests/compiler/nim.cfg | 7 +++ tests/compiler/tasciitables.nim | 109 +++++++++++++++++++++++++++++++++++++ tests/compiler/tunittest_light.nim | 55 +++++++++++++++++++ 5 files changed, 291 insertions(+) create mode 100644 compiler/asciitables.nim create mode 100644 compiler/unittest_light.nim create mode 100644 tests/compiler/nim.cfg create mode 100644 tests/compiler/tasciitables.nim create mode 100644 tests/compiler/tunittest_light.nim diff --git a/compiler/asciitables.nim b/compiler/asciitables.nim new file mode 100644 index 000000000..c25d54bde --- /dev/null +++ b/compiler/asciitables.nim @@ -0,0 +1,83 @@ +#[ +move to std/asciitables.nim once stable, or to a nimble paackage +once compiler can depend on nimble +]# + +type Cell* = object + text*: string + width*, row*, col*, ncols*, nrows*: int + +iterator parseTableCells*(s: string, delim = '\t'): Cell = + ## iterates over all cells in a `delim`-delimited `s`, after a 1st + ## pass that computes number of rows, columns, and width of each column. + var widths: seq[int] + var cell: Cell + template update() = + if widths.len<=cell.col: + widths.setLen cell.col+1 + widths[cell.col] = cell.width + else: + widths[cell.col] = max(widths[cell.col], cell.width) + cell.width = 0 + + for a in s: + case a + of '\n': + update() + cell.col = 0 + cell.row.inc + elif a == delim: + update() + cell.col.inc + else: + # todo: consider multi-width chars when porting to non-ascii implementation + cell.width.inc + if s.len > 0 and s[^1] != '\n': + update() + + cell.ncols = widths.len + cell.nrows = cell.row + 1 + cell.row = 0 + cell.col = 0 + cell.width = 0 + + template update2() = + cell.width = widths[cell.col] + yield cell + cell.text = "" + cell.width = 0 + cell.col.inc + + template finishRow() = + for col in cell.col.. 0 and s[^1] != '\n': + finishRow() + +proc alignTable*(s: string, delim = '\t', fill = ' ', sep = " "): string = + ## formats a `delim`-delimited `s` representing a table; each cell is aligned + ## to a width that's computed for each column; consecutive columns are + ## delimted by `sep`, and alignment space is filled using `fill`. + ## More customized formatting can be done by calling `parseTableCells` directly. + for cell in parseTableCells(s, delim): + result.add cell.text + for i in cell.text.len.. Date: Wed, 9 Jan 2019 10:46:23 +0100 Subject: epoll selector starts with reasonable fd set size (1024) and increases in powers of two when needed. This prevents the selector to allocate large amounts of memory at startup on systems with a high RLIMIT_NOFILE setting (#10194) --- lib/pure/ioselects/ioselectors_epoll.nim | 22 +++++++++++++++++++--- lib/pure/selectors.nim | 3 +++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/pure/ioselects/ioselectors_epoll.nim b/lib/pure/ioselects/ioselectors_epoll.nim index 16d901ff0..ffd60120e 100644 --- a/lib/pure/ioselects/ioselectors_epoll.nim +++ b/lib/pure/ioselects/ioselectors_epoll.nim @@ -53,6 +53,7 @@ when hasThreadSupport: SelectorImpl[T] = object epollFD: cint maxFD: int + numFD: int fds: ptr SharedArray[SelectorKey[T]] count: int Selector*[T] = ptr SelectorImpl[T] @@ -61,6 +62,7 @@ else: SelectorImpl[T] = object epollFD: cint maxFD: int + numFD: int fds: seq[SelectorKey[T]] count: int Selector*[T] = ref SelectorImpl[T] @@ -76,6 +78,8 @@ proc newSelector*[T](): Selector[T] = raiseOsError(osLastError()) var maxFD = int(a.rlim_max) doAssert(maxFD > 0) + # Start with a reasonable size, checkFd() will grow this on demand + const numFD = 1024 var epollFD = epoll_create(MAX_EPOLL_EVENTS) if epollFD < 0: @@ -85,14 +89,16 @@ proc newSelector*[T](): Selector[T] = result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T]))) result.epollFD = epollFD result.maxFD = maxFD - result.fds = allocSharedArray[SelectorKey[T]](maxFD) + result.numFD = numFD + result.fds = allocSharedArray[SelectorKey[T]](numFD) else: result = Selector[T]() result.epollFD = epollFD result.maxFD = maxFD - result.fds = newSeq[SelectorKey[T]](maxFD) + result.numFD = numFD + result.fds = newSeq[SelectorKey[T]](numFD) - for i in 0 ..< maxFD: + for i in 0 ..< numFD: result.fds[i].ident = InvalidIdent proc close*[T](s: Selector[T]) = @@ -127,6 +133,16 @@ template checkFd(s, f) = # FD if there is too many. -- DP if f >= s.maxFD: raiseIOSelectorsError("Maximum number of descriptors is exhausted!") + if f >= s.numFD: + var numFD = s.numFD + while numFD <= f: numFD *= 2 + when hasThreadSupport: + s.fds = reallocSharedArray(s.fds, numFD) + else: + s.fds.setLen(numFD) + for i in s.numFD ..< numFD: + s.fds[i].ident = InvalidIdent + s.numFD = numFD proc registerHandle*[T](s: Selector[T], fd: int | SocketHandle, events: set[Event], data: T) = diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim index e4c2b2124..b9c834127 100644 --- a/lib/pure/selectors.nim +++ b/lib/pure/selectors.nim @@ -239,6 +239,9 @@ else: proc allocSharedArray[T](nsize: int): ptr SharedArray[T] = result = cast[ptr SharedArray[T]](allocShared0(sizeof(T) * nsize)) + proc reallocSharedArray[T](sa: ptr SharedArray[T], nsize: int): ptr SharedArray[T] = + result = cast[ptr SharedArray[T]](reallocShared(sa, sizeof(T) * nsize)) + proc deallocSharedArray[T](sa: ptr SharedArray[T]) = deallocShared(cast[pointer](sa)) type -- cgit 1.4.1-2-gfad0 From 11050d110461edc3646a66b8afefb7907187f78c Mon Sep 17 00:00:00 2001 From: Notkea Date: Wed, 9 Jan 2019 15:06:40 +0100 Subject: make Stream.{read,peek} procs public (#9806) Those are useful in generic code, and `proc write*[T](s: Stream, x: T)` was already public. --- lib/pure/streams.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index b0ac62525..b5254b4f7 100644 --- a/lib/pure/streams.nim +++ b/lib/pure/streams.nim @@ -146,12 +146,12 @@ proc writeLine*(s: Stream, args: varargs[string, `$`]) = for str in args: write(s, str) write(s, "\n") -proc read[T](s: Stream, result: var T) = +proc read*[T](s: Stream, result: var T) = ## generic read procedure. Reads `result` from the stream `s`. if readData(s, addr(result), sizeof(T)) != sizeof(T): raise newEIO("cannot read from stream") -proc peek[T](s: Stream, result: var T) = +proc peek*[T](s: Stream, result: var T) = ## generic peek procedure. Peeks `result` from the stream `s`. if peekData(s, addr(result), sizeof(T)) != sizeof(T): raise newEIO("cannot read from stream") -- cgit 1.4.1-2-gfad0 From 87232a38e4fc40a3acb154a78bf32f8ea5734d13 Mon Sep 17 00:00:00 2001 From: Oscar Nihlgård Date: Thu, 10 Jan 2019 08:49:57 +0100 Subject: ValueError now inherits from CatchableError (#10246) --- changelog.md | 4 +++- lib/system.nim | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 35d2adbd1..87885aec9 100644 --- a/changelog.md +++ b/changelog.md @@ -30,7 +30,9 @@ - `osproc.execProcess` now also takes a `workingDir` parameter. -- `options.UnpackError` is no longer a ref type and inherits from `System.Defect` instead of `System.ValueError`. +- `options.UnpackError` is no longer a ref type and inherits from `system.Defect` instead of `system.ValueError`. + +- `system.ValueError` now inherits from `system.CatchableError` instead of `system.Defect`. - nre's `RegexMatch.{captureBounds,captures}[]` no longer return `Option` or `nil`/`""`, respectivly. Use the newly added `n in p.captures` method to diff --git a/lib/system.nim b/lib/system.nim index c68eba2e0..8a35714a5 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -606,7 +606,7 @@ type ## Raised when assertion is proved wrong. ## ## Usually the result of using the `assert() template <#assert>`_. - ValueError* = object of Defect ## \ + ValueError* = object of CatchableError ## \ ## Raised for string and object conversion errors. KeyError* = object of ValueError ## \ ## Raised if a key cannot be found in a table. -- cgit 1.4.1-2-gfad0 From da57c0ab99eadaee1aaecfab1688a9382e96edb9 Mon Sep 17 00:00:00 2001 From: narimiran Date: Tue, 8 Jan 2019 17:04:07 +0100 Subject: remove `subexes` --- doc/lib.rst | 3 - lib/pure/subexes.nim | 406 ---------------------------------------------- tests/test_nimscript.nims | 1 - tools/kochdocs.nim | 1 - 4 files changed, 411 deletions(-) delete mode 100644 lib/pure/subexes.nim diff --git a/doc/lib.rst b/doc/lib.rst index a16bf2677..569e3f845 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -141,9 +141,6 @@ String handling Ropes can represent very long strings efficiently; especially concatenation is done in O(1) instead of O(n). -* `subexes `_ - This module implements advanced string substitution operations. - * `std/editdistance `_ This module contains an algorithm to compute the edit distance between two Unicode strings. diff --git a/lib/pure/subexes.nim b/lib/pure/subexes.nim deleted file mode 100644 index 638e71f04..000000000 --- a/lib/pure/subexes.nim +++ /dev/null @@ -1,406 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## Nim support for `substitution expressions`:idx: (`subex`:idx:). -## -## .. include:: ../../doc/subexes.txt -## - -{.push debugger:off .} # the user does not want to trace a part - # of the standard library! - -from strutils import parseInt, cmpIgnoreStyle, Digits -include "system/inclrtl" -import system/helpers2 - -proc findNormalized(x: string, inArray: openarray[string]): int = - var i = 0 - while i < high(inArray): - if cmpIgnoreStyle(x, inArray[i]) == 0: return i - inc(i, 2) # incrementing by 1 would probably lead to a - # security hole... - return -1 - -type - SubexError* = object of ValueError ## exception that is raised for - ## an invalid subex - -proc raiseInvalidFormat(msg: string) {.noinline.} = - raise newException(SubexError, "invalid format string: " & msg) - -type - FormatParser = object {.pure, final.} - when defined(js): - f: string # we rely on the '\0' terminator - # which JS's native string doesn't have - else: - f: cstring - num, i, lineLen: int - -template call(x: untyped): untyped = - p.i = i - x - i = p.i - -template callNoLineLenTracking(x: untyped): untyped = - let oldLineLen = p.lineLen - p.i = i - x - i = p.i - p.lineLen = oldLineLen - -proc getFormatArg(p: var FormatParser, a: openArray[string]): int = - const PatternChars = {'a'..'z', 'A'..'Z', '0'..'9', '\128'..'\255', '_'} - var i = p.i - var f = p.f - case f[i] - of '#': - result = p.num - inc i - inc p.num - of '1'..'9', '-': - var j = 0 - var negative = f[i] == '-' - if negative: inc i - while f[i] in Digits: - j = j * 10 + ord(f[i]) - ord('0') - inc i - result = if not negative: j-1 else: a.len-j - of 'a'..'z', 'A'..'Z', '\128'..'\255', '_': - var name = "" - while f[i] in PatternChars: - name.add(f[i]) - inc(i) - result = findNormalized(name, a)+1 - of '$': - inc(i) - call: - result = getFormatArg(p, a) - result = parseInt(a[result])-1 - else: - raiseInvalidFormat("'#', '$', number or identifier expected") - if result >=% a.len: raiseInvalidFormat(formatErrorIndexBound(result, a.len)) - p.i = i - -proc scanDollar(p: var FormatParser, a: openarray[string], s: var string) {. - noSideEffect.} - -proc emitChar(p: var FormatParser, x: var string, ch: char) {.inline.} = - x.add(ch) - if ch == '\L': p.lineLen = 0 - else: inc p.lineLen - -proc emitStrLinear(p: var FormatParser, x: var string, y: string) {.inline.} = - for ch in items(y): emitChar(p, x, ch) - -proc emitStr(p: var FormatParser, x: var string, y: string) {.inline.} = - x.add(y) - inc p.lineLen, y.len - -proc scanQuote(p: var FormatParser, x: var string, toAdd: bool) = - var i = p.i+1 - var f = p.f - while true: - if f[i] == '\'': - inc i - if f[i] != '\'': break - inc i - if toAdd: emitChar(p, x, '\'') - elif f[i] == '\0': raiseInvalidFormat("closing \"'\" expected") - else: - if toAdd: emitChar(p, x, f[i]) - inc i - p.i = i - -proc scanBranch(p: var FormatParser, a: openArray[string], - x: var string, choice: int) = - var i = p.i - var f = p.f - var c = 0 - var elsePart = i - var toAdd = choice == 0 - while true: - case f[i] - of ']': break - of '|': - inc i - elsePart = i - inc c - if toAdd: break - toAdd = choice == c - of '\'': - call: scanQuote(p, x, toAdd) - of '\0': raiseInvalidFormat("closing ']' expected") - else: - if toAdd: - if f[i] == '$': - inc i - call: scanDollar(p, a, x) - else: - emitChar(p, x, f[i]) - inc i - else: - inc i - if not toAdd and choice >= 0: - # evaluate 'else' part: - var last = i - i = elsePart - while true: - case f[i] - of '|', ']': break - of '\'': - call: scanQuote(p, x, true) - of '$': - inc i - call: scanDollar(p, a, x) - else: - emitChar(p, x, f[i]) - inc i - i = last - p.i = i+1 - -proc scanSlice(p: var FormatParser, a: openarray[string]): tuple[x, y: int] = - var slice = false - var i = p.i - var f = p.f - - if f[i] == '{': inc i - else: raiseInvalidFormat("'{' expected") - if f[i] == '.' and f[i+1] == '.': - inc i, 2 - slice = true - else: - call: result.x = getFormatArg(p, a) - if f[i] == '.' and f[i+1] == '.': - inc i, 2 - slice = true - if slice: - if f[i] != '}': - call: result.y = getFormatArg(p, a) - else: - result.y = high(a) - else: - result.y = result.x - if f[i] != '}': raiseInvalidFormat("'}' expected") - inc i - p.i = i - -proc scanDollar(p: var FormatParser, a: openarray[string], s: var string) = - var i = p.i - var f = p.f - case f[i] - of '$': - emitChar p, s, '$' - inc i - of '*': - for j in 0..a.high: emitStr p, s, a[j] - inc i - of '{': - call: - let (x, y) = scanSlice(p, a) - for j in x..y: emitStr p, s, a[j] - of '[': - inc i - var start = i - call: scanBranch(p, a, s, -1) - var x: int - if f[i] == '{': - inc i - call: x = getFormatArg(p, a) - if f[i] != '}': raiseInvalidFormat("'}' expected") - inc i - else: - call: x = getFormatArg(p, a) - var last = i - let choice = parseInt(a[x]) - i = start - call: scanBranch(p, a, s, choice) - i = last - of '\'': - var sep = "" - callNoLineLenTracking: scanQuote(p, sep, true) - if f[i] == '~': - # $' '~{1..3} - # insert space followed by 1..3 if not empty - inc i - call: - let (x, y) = scanSlice(p, a) - var L = 0 - for j in x..y: inc L, a[j].len - if L > 0: - emitStrLinear p, s, sep - for j in x..y: emitStr p, s, a[j] - else: - block StringJoin: - block OptionalLineLengthSpecifier: - var maxLen = 0 - case f[i] - of '0'..'9': - while f[i] in Digits: - maxLen = maxLen * 10 + ord(f[i]) - ord('0') - inc i - of '$': - # do not skip the '$' here for `getFormatArg`! - call: - maxLen = getFormatArg(p, a) - else: break OptionalLineLengthSpecifier - var indent = "" - case f[i] - of 'i': - inc i - callNoLineLenTracking: scanQuote(p, indent, true) - - call: - let (x, y) = scanSlice(p, a) - if maxLen < 1: emitStrLinear(p, s, indent) - var items = 1 - emitStr p, s, a[x] - for j in x+1..y: - emitStr p, s, sep - if items >= maxLen: - emitStrLinear p, s, indent - items = 0 - emitStr p, s, a[j] - inc items - of 'c': - inc i - callNoLineLenTracking: scanQuote(p, indent, true) - - call: - let (x, y) = scanSlice(p, a) - if p.lineLen + a[x].len > maxLen: emitStrLinear(p, s, indent) - emitStr p, s, a[x] - for j in x+1..y: - emitStr p, s, sep - if p.lineLen + a[j].len > maxLen: emitStrLinear(p, s, indent) - emitStr p, s, a[j] - - else: raiseInvalidFormat("unit 'c' (chars) or 'i' (items) expected") - break StringJoin - - call: - let (x, y) = scanSlice(p, a) - emitStr p, s, a[x] - for j in x+1..y: - emitStr p, s, sep - emitStr p, s, a[j] - else: - call: - var x = getFormatArg(p, a) - emitStr p, s, a[x] - p.i = i - - -type - Subex* = distinct string ## string that contains a substitution expression - -proc subex*(s: string): Subex = - ## constructs a *substitution expression* from `s`. Currently this performs - ## no syntax checking but this may change in later versions. - result = Subex(s) - -proc addf*(s: var string, formatstr: Subex, a: varargs[string, `$`]) {. - noSideEffect, rtl, extern: "nfrmtAddf".} = - ## The same as ``add(s, formatstr % a)``, but more efficient. - var p: FormatParser - p.f = formatstr.string - var i = 0 - while i < len(formatstr.string): - if p.f[i] == '$': - inc i - call: scanDollar(p, a, s) - else: - emitChar(p, s, p.f[i]) - inc(i) - -proc `%` *(formatstr: Subex, a: openarray[string]): string {.noSideEffect, - rtl, extern: "nfrmtFormatOpenArray".} = - ## The `substitution`:idx: operator performs string substitutions in - ## `formatstr` and returns a modified `formatstr`. This is often called - ## `string interpolation`:idx:. - ## - result = newStringOfCap(formatstr.string.len + a.len shl 4) - addf(result, formatstr, a) - -proc `%` *(formatstr: Subex, a: string): string {.noSideEffect, - rtl, extern: "nfrmtFormatSingleElem".} = - ## This is the same as ``formatstr % [a]``. - result = newStringOfCap(formatstr.string.len + a.len) - addf(result, formatstr, [a]) - -proc format*(formatstr: Subex, a: varargs[string, `$`]): string {.noSideEffect, - rtl, extern: "nfrmtFormatVarargs".} = - ## The `substitution`:idx: operator performs string substitutions in - ## `formatstr` and returns a modified `formatstr`. This is often called - ## `string interpolation`:idx:. - ## - result = newStringOfCap(formatstr.string.len + a.len shl 4) - addf(result, formatstr, a) - -{.pop.} - -when isMainModule: - from strutils import replace - - proc `%`(formatstr: string, a: openarray[string]): string = - result = newStringOfCap(formatstr.len + a.len shl 4) - addf(result, formatstr.Subex, a) - - proc `%`(formatstr: string, a: string): string = - result = newStringOfCap(formatstr.len + a.len) - addf(result, formatstr.Subex, [a]) - - - doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c" - doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] == - "The cat eats fish." - - - doAssert "$[abc|def]# $3 $# $#" % ["17", "b", "c"] == "def c b c" - doAssert "$[abc|def]# $3 $# $#" % ["1", "b", "c"] == "def c b c" - doAssert "$[abc|def]# $3 $# $#" % ["0", "b", "c"] == "abc c b c" - doAssert "$[abc|def|]# $3 $# $#" % ["17", "b", "c"] == " c b c" - - doAssert "$[abc|def|]# $3 $# $#" % ["-9", "b", "c"] == " c b c" - doAssert "$1($', '{2..})" % ["f", "a", "b"] == "f(a, b)" - - doAssert "$[$1($', '{2..})|''''|fg'$3']1" % ["7", "a", "b"] == "fg$3" - - doAssert "$[$#($', '{#..})|''''|$3]1" % ["0", "a", "b"] == "0(a, b)" - doAssert "$' '~{..}" % "" == "" - doAssert "$' '~{..}" % "P0" == " P0" - doAssert "${$1}" % "1" == "1" - doAssert "${$$-1} $$1" % "1" == "1 $1" - - doAssert(("$#($', '10c'\n '{#..})" % ["doAssert", "longishA", "longish"]).replace(" \n", "\n") == - """doAssert( - longishA, - longish)""") - - doAssert(("type MyEnum* = enum\n $', '2i'\n '{..}" % ["fieldA", - "fieldB", "FiledClkad", "fieldD", "fieldE", "longishFieldName"]).replace(" \n", "\n") == - strutils.unindent(""" - type MyEnum* = enum - fieldA, fieldB, - FiledClkad, fieldD, - fieldE, longishFieldName""", 6)) - - doAssert subex"$1($', '{2..})" % ["f", "a", "b", "c"] == "f(a, b, c)" - - doAssert subex"$1 $[files|file|files]{1} copied" % ["1"] == "1 file copied" - - doAssert subex"$['''|'|''''|']']#" % "0" == "'|" - - doAssert((subex("type\n Enum = enum\n $', '40c'\n '{..}") % [ - "fieldNameA", "fieldNameB", "fieldNameC", "fieldNameD"]).replace(" \n", "\n") == - strutils.unindent(""" - type - Enum = enum - fieldNameA, fieldNameB, fieldNameC, - fieldNameD""", 6)) diff --git a/tests/test_nimscript.nims b/tests/test_nimscript.nims index b9a6097c2..3efbb0a4c 100644 --- a/tests/test_nimscript.nims +++ b/tests/test_nimscript.nims @@ -17,7 +17,6 @@ import parseutils import deques import sequtils import strutils -import subexes import tables import unicode import uri diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim index 19b0b3d78..b455a517a 100644 --- a/tools/kochdocs.nim +++ b/tools/kochdocs.nim @@ -195,7 +195,6 @@ lib/pure/collections/sequtils.nim lib/pure/collections/rtarrays.nim lib/pure/cookies.nim lib/pure/memfiles.nim -lib/pure/subexes.nim lib/pure/collections/critbits.nim lib/core/locks.nim lib/core/rlocks.nim -- cgit 1.4.1-2-gfad0 From 8d9b093440939a890d9f7df1ffe037e90f6d7a6c Mon Sep 17 00:00:00 2001 From: narimiran Date: Tue, 8 Jan 2019 17:19:34 +0100 Subject: remove `scgi` --- doc/lib.rst | 3 - lib/pure/scgi.nim | 295 ----------------------------------- tests/bind/tnicerrorforsymchoice.nim | 9 +- tools/kochdocs.nim | 1 - 4 files changed, 7 insertions(+), 301 deletions(-) delete mode 100644 lib/pure/scgi.nim diff --git a/doc/lib.rst b/doc/lib.rst index 569e3f845..6b1af43e1 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -230,9 +230,6 @@ Internet Protocols and Support * `cgi `_ This module implements helpers for CGI applications. -* `scgi `_ - This module implements helpers for SCGI applications. - * `browsers `_ This module implements procs for opening URLs with the user's default browser. diff --git a/lib/pure/scgi.nim b/lib/pure/scgi.nim deleted file mode 100644 index e36803823..000000000 --- a/lib/pure/scgi.nim +++ /dev/null @@ -1,295 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2013 Andreas Rumpf, Dominik Picheta -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements helper procs for SCGI applications. Example: -## -## .. code-block:: Nim -## -## import strtabs, sockets, scgi -## -## var counter = 0 -## proc handleRequest(client: Socket, input: string, -## headers: StringTableRef): bool {.procvar.} = -## inc(counter) -## client.writeStatusOkTextContent() -## client.send("Hello for the $#th time." % $counter & "\c\L") -## return false # do not stop processing -## -## run(handleRequest) -## -## **Warning:** The API of this module is unstable, and therefore is subject -## to change. -## -## **Warning:** This module only supports the old asynchronous interface. -## You may wish to use the `asynchttpserver `_ -## instead for web applications. - -include "system/inclrtl" - -import sockets, strutils, os, strtabs, asyncio - -type - ScgiError* = object of IOError ## the exception that is raised, if a SCGI error occurs - -proc raiseScgiError*(msg: string) {.noreturn.} = - ## raises an ScgiError exception with message `msg`. - var e: ref ScgiError - new(e) - e.msg = msg - raise e - -proc parseWord(inp: string, outp: var string, start: int): int = - result = start - while inp[result] != '\0': inc(result) - outp = substr(inp, start, result-1) - -proc parseHeaders(s: string, L: int): StringTableRef = - result = newStringTable() - var i = 0 - while i < L: - var key, val: string - i = parseWord(s, key, i)+1 - i = parseWord(s, val, i)+1 - result[key] = val - if s[i] == ',': inc(i) - else: raiseScgiError("',' after netstring expected") - -proc recvChar(s: Socket): char = - var c: char - if recv(s, addr(c), sizeof(c)) == sizeof(c): - result = c - -type - ScgiState* = object of RootObj ## SCGI state object - server: Socket - bufLen: int - client*: Socket ## the client socket to send data to - headers*: StringTableRef ## the parsed headers - input*: string ## the input buffer - - - # Async - - ClientMode = enum - ClientReadChar, ClientReadHeaders, ClientReadContent - - AsyncClient = ref object - c: AsyncSocket - mode: ClientMode - dataLen: int - headers: StringTableRef ## the parsed headers - input: string ## the input buffer - - AsyncScgiStateObj = object - handleRequest: proc (client: AsyncSocket, - input: string, - headers: StringTableRef) {.closure, gcsafe.} - asyncServer: AsyncSocket - disp: Dispatcher - AsyncScgiState* = ref AsyncScgiStateObj - -proc recvBuffer(s: var ScgiState, L: int) = - if L > s.bufLen: - s.bufLen = L - s.input = newString(L) - if L > 0 and recv(s.client, cstring(s.input), L) != L: - raiseScgiError("could not read all data") - setLen(s.input, L) - -proc open*(s: var ScgiState, port = Port(4000), address = "127.0.0.1", - reuseAddr = false) = - ## opens a connection. - s.bufLen = 4000 - s.input = newString(s.bufLen) # will be reused - - s.server = socket() - if s.server == invalidSocket: raiseOSError(osLastError()) - new(s.client) # Initialise s.client for `next` - if s.server == invalidSocket: raiseScgiError("could not open socket") - #s.server.connect(connectionName, port) - if reuseAddr: - s.server.setSockOpt(OptReuseAddr, true) - bindAddr(s.server, port, address) - listen(s.server) - -proc close*(s: var ScgiState) = - ## closes the connection. - s.server.close() - -proc next*(s: var ScgiState, timeout: int = -1): bool = - ## proceed to the first/next request. Waits ``timeout`` milliseconds for a - ## request, if ``timeout`` is `-1` then this function will never time out. - ## Returns `true` if a new request has been processed. - var rsocks = @[s.server] - if select(rsocks, timeout) == 1 and rsocks.len == 1: - new(s.client) - accept(s.server, s.client) - var L = 0 - while true: - var d = s.client.recvChar() - if d == '\0': - s.client.close() - return false - if d notin strutils.Digits: - if d != ':': raiseScgiError("':' after length expected") - break - L = L * 10 + ord(d) - ord('0') - recvBuffer(s, L+1) - s.headers = parseHeaders(s.input, L) - if s.headers.getOrDefault("SCGI") != "1": raiseScgiError("SCGI Version 1 expected") - L = parseInt(s.headers.getOrDefault("CONTENT_LENGTH")) - recvBuffer(s, L) - return true - -proc writeStatusOkTextContent*(c: Socket, contentType = "text/html") = - ## sends the following string to the socket `c`:: - ## - ## Status: 200 OK\r\LContent-Type: text/html\r\L\r\L - ## - ## You should send this before sending your HTML page, for example. - c.send("Status: 200 OK\r\L" & - "Content-Type: $1\r\L\r\L" % contentType) - -proc run*(handleRequest: proc (client: Socket, input: string, - headers: StringTableRef): bool {.nimcall,gcsafe.}, - port = Port(4000)) = - ## encapsulates the SCGI object and main loop. - var s: ScgiState - s.open(port) - var stop = false - while not stop: - if next(s): - stop = handleRequest(s.client, s.input, s.headers) - s.client.close() - s.close() - -# -- AsyncIO start - -proc recvBufferAsync(client: AsyncClient, L: int): ReadLineResult = - result = ReadPartialLine - var data = "" - if L < 1: - raiseScgiError("Cannot read negative or zero length: " & $L) - let ret = recvAsync(client.c, data, L) - if ret == 0 and data == "": - client.c.close() - return ReadDisconnected - if ret == -1: - return ReadNone # No more data available - client.input.add(data) - if ret == L: - return ReadFullLine - -proc checkCloseSocket(client: AsyncClient) = - if not client.c.isClosed: - if client.c.isSendDataBuffered: - client.c.setHandleWrite do (s: AsyncSocket): - if not s.isClosed and not s.isSendDataBuffered: - s.close() - s.delHandleWrite() - else: client.c.close() - -proc handleClientRead(client: AsyncClient, s: AsyncScgiState) = - case client.mode - of ClientReadChar: - while true: - var d = "" - let ret = client.c.recvAsync(d, 1) - if d == "" and ret == 0: - # Disconnected - client.c.close() - return - if ret == -1: - return # No more data available - if d[0] notin strutils.Digits: - if d[0] != ':': raiseScgiError("':' after length expected") - break - client.dataLen = client.dataLen * 10 + ord(d[0]) - ord('0') - client.mode = ClientReadHeaders - handleClientRead(client, s) # Allow progression - of ClientReadHeaders: - let ret = recvBufferAsync(client, (client.dataLen+1)-client.input.len) - case ret - of ReadFullLine: - client.headers = parseHeaders(client.input, client.input.len-1) - if client.headers.getOrDefault("SCGI") != "1": raiseScgiError("SCGI Version 1 expected") - client.input = "" # For next part - - let contentLen = parseInt(client.headers.getOrDefault("CONTENT_LENGTH")) - if contentLen > 0: - client.mode = ClientReadContent - else: - s.handleRequest(client.c, client.input, client.headers) - checkCloseSocket(client) - of ReadPartialLine, ReadDisconnected, ReadNone: return - of ClientReadContent: - let L = parseInt(client.headers.getOrDefault("CONTENT_LENGTH")) - - client.input.len - if L > 0: - let ret = recvBufferAsync(client, L) - case ret - of ReadFullLine: - s.handleRequest(client.c, client.input, client.headers) - checkCloseSocket(client) - of ReadPartialLine, ReadDisconnected, ReadNone: return - else: - s.handleRequest(client.c, client.input, client.headers) - checkCloseSocket(client) - -proc handleAccept(sock: AsyncSocket, s: AsyncScgiState) = - var client: AsyncSocket - new(client) - accept(s.asyncServer, client) - var asyncClient = AsyncClient(c: client, mode: ClientReadChar, dataLen: 0, - headers: newStringTable(), input: "") - client.handleRead = - proc (sock: AsyncSocket) = - handleClientRead(asyncClient, s) - s.disp.register(client) - -proc open*(handleRequest: proc (client: AsyncSocket, - input: string, headers: StringTableRef) {. - closure, gcsafe.}, - port = Port(4000), address = "127.0.0.1", - reuseAddr = false): AsyncScgiState = - ## Creates an ``AsyncScgiState`` object which serves as a SCGI server. - ## - ## After the execution of ``handleRequest`` the client socket will be closed - ## automatically unless it has already been closed. - var cres: AsyncScgiState - new(cres) - cres.asyncServer = asyncSocket() - cres.asyncServer.handleAccept = proc (s: AsyncSocket) = handleAccept(s, cres) - if reuseAddr: - cres.asyncServer.setSockOpt(OptReuseAddr, true) - bindAddr(cres.asyncServer, port, address) - listen(cres.asyncServer) - cres.handleRequest = handleRequest - result = cres - -proc register*(d: Dispatcher, s: AsyncScgiState): Delegate {.discardable.} = - ## Registers ``s`` with dispatcher ``d``. - result = d.register(s.asyncServer) - s.disp = d - -proc close*(s: AsyncScgiState) = - ## Closes the ``AsyncScgiState``. - s.asyncServer.close() - -when false: - var counter = 0 - proc handleRequest(client: Socket, input: string, - headers: StringTableRef): bool {.procvar.} = - inc(counter) - client.writeStatusOkTextContent() - client.send("Hello for the $#th time." % $counter & "\c\L") - return false # do not stop processing - - run(handleRequest) - diff --git a/tests/bind/tnicerrorforsymchoice.nim b/tests/bind/tnicerrorforsymchoice.nim index f16b323de..c06926805 100644 --- a/tests/bind/tnicerrorforsymchoice.nim +++ b/tests/bind/tnicerrorforsymchoice.nim @@ -1,10 +1,15 @@ discard """ errormsg: "type mismatch: got " - line: 18 + line: 23 """ +# Fake ScgiState objects, from now-deprecated scgi module +type + ScgiState* = object of RootObj ## SCGI state object + AsyncScgiState* = object of RootObj ## SCGI state object + #bug #442 -import scgi, sockets, asyncio, strtabs +import sockets, asyncio, strtabs proc handleSCGIRequest[TScgi: ScgiState | AsyncScgiState](s: TScgi) = discard proc handleSCGIRequest(client: AsyncSocket, headers: StringTableRef, diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim index b455a517a..9a502a256 100644 --- a/tools/kochdocs.nim +++ b/tools/kochdocs.nim @@ -177,7 +177,6 @@ lib/pure/colors.nim lib/pure/mimetypes.nim lib/pure/json.nim lib/pure/base64.nim -lib/pure/scgi.nim lib/impure/nre.nim lib/impure/nre/private/util.nim lib/deprecated/pure/sockets.nim -- cgit 1.4.1-2-gfad0 From 1a3763fe133690a4faf4300d2e2c48e7a9c06f2a Mon Sep 17 00:00:00 2001 From: narimiran Date: Tue, 8 Jan 2019 17:26:58 +0100 Subject: remove `smtp` --- doc/lib.rst | 3 - lib/pure/smtp.nim | 253 -------------------------------------------------- lib/pure/smtp.nim.cfg | 1 - tools/kochdocs.nim | 1 - 4 files changed, 258 deletions(-) delete mode 100644 lib/pure/smtp.nim delete mode 100644 lib/pure/smtp.nim.cfg diff --git a/doc/lib.rst b/doc/lib.rst index 6b1af43e1..fdc188ef2 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -238,9 +238,6 @@ Internet Protocols and Support This module implements a simple HTTP client which supports both synchronous and asynchronous retrieval of web pages. -* `smtp `_ - This module implement a simple SMTP client. - * `cookies `_ This module contains helper procs for parsing and generating cookies. diff --git a/lib/pure/smtp.nim b/lib/pure/smtp.nim deleted file mode 100644 index 5f4b09f80..000000000 --- a/lib/pure/smtp.nim +++ /dev/null @@ -1,253 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Dominik Picheta -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements the SMTP client protocol as specified by RFC 5321, -## this can be used to send mail to any SMTP Server. -## -## This module also implements the protocol used to format messages, -## as specified by RFC 2822. -## -## Example gmail use: -## -## -## .. code-block:: Nim -## var msg = createMessage("Hello from Nim's SMTP", -## "Hello!.\n Is this awesome or what?", -## @["foo@gmail.com"]) -## let smtpConn = newSmtp(useSsl = true, debug=true) -## smtpConn.connect("smtp.gmail.com", Port 465) -## smtpConn.auth("username", "password") -## smtpConn.sendmail("username@gmail.com", @["foo@gmail.com"], $msg) -## -## -## For SSL support this module relies on OpenSSL. If you want to -## enable SSL, compile with ``-d:ssl``. - -import net, strutils, strtabs, base64, os -import asyncnet, asyncdispatch - -export Port - -type - Message* = object - msgTo: seq[string] - msgCc: seq[string] - msgSubject: string - msgOtherHeaders: StringTableRef - msgBody: string - - ReplyError* = object of IOError - - SmtpBase[SocketType] = ref object - sock: SocketType - debug: bool - - Smtp* = SmtpBase[Socket] - AsyncSmtp* = SmtpBase[AsyncSocket] - -proc debugSend(smtp: Smtp | AsyncSmtp, cmd: string) {.multisync.} = - if smtp.debug: - echo("C:" & cmd) - await smtp.sock.send(cmd) - -proc debugRecv(smtp: Smtp | AsyncSmtp): Future[TaintedString] {.multisync.} = - result = await smtp.sock.recvLine() - if smtp.debug: - echo("S:" & result.string) - -proc quitExcpt(smtp: Smtp, msg: string) = - smtp.debugSend("QUIT") - raise newException(ReplyError, msg) - -const compiledWithSsl = defined(ssl) - -when not defined(ssl): - type PSSLContext = ref object - let defaultSSLContext: PSSLContext = nil -else: - var defaultSSLContext {.threadvar.}: SSLContext - - proc getSSLContext(): SSLContext = - if defaultSSLContext == nil: - defaultSSLContext = newContext(verifyMode = CVerifyNone) - result = defaultSSLContext - -proc createMessage*(mSubject, mBody: string, mTo, mCc: seq[string], - otherHeaders: openarray[tuple[name, value: string]]): Message = - ## Creates a new MIME compliant message. - result.msgTo = mTo - result.msgCc = mCc - result.msgSubject = mSubject - result.msgBody = mBody - result.msgOtherHeaders = newStringTable() - for n, v in items(otherHeaders): - result.msgOtherHeaders[n] = v - -proc createMessage*(mSubject, mBody: string, mTo, - mCc: seq[string] = @[]): Message = - ## Alternate version of the above. - result.msgTo = mTo - result.msgCc = mCc - result.msgSubject = mSubject - result.msgBody = mBody - result.msgOtherHeaders = newStringTable() - -proc `$`*(msg: Message): string = - ## stringify for ``Message``. - result = "" - if msg.msgTo.len() > 0: - result = "TO: " & msg.msgTo.join(", ") & "\c\L" - if msg.msgCc.len() > 0: - result.add("CC: " & msg.msgCc.join(", ") & "\c\L") - # TODO: Folding? i.e when a line is too long, shorten it... - result.add("Subject: " & msg.msgSubject & "\c\L") - for key, value in pairs(msg.msgOtherHeaders): - result.add(key & ": " & value & "\c\L") - - result.add("\c\L") - result.add(msg.msgBody) - -proc newSmtp*(useSsl = false, debug=false, - sslContext: SSLContext = nil): Smtp = - ## Creates a new ``Smtp`` instance. - new result - result.debug = debug - result.sock = newSocket() - if useSsl: - when compiledWithSsl: - if sslContext == nil: - getSSLContext().wrapSocket(result.sock) - else: - sslContext.wrapSocket(result.sock) - else: - {.error: "SMTP module compiled without SSL support".} - -proc newAsyncSmtp*(useSsl = false, debug=false, - sslContext: SSLContext = nil): AsyncSmtp = - ## Creates a new ``AsyncSmtp`` instance. - new result - result.debug = debug - - result.sock = newAsyncSocket() - if useSsl: - when compiledWithSsl: - if sslContext == nil: - getSSLContext().wrapSocket(result.sock) - else: - sslContext.wrapSocket(result.sock) - else: - {.error: "SMTP module compiled without SSL support".} - -proc quitExcpt(smtp: AsyncSmtp, msg: string): Future[void] = - var retFuture = newFuture[void]() - var sendFut = smtp.debugSend("QUIT") - sendFut.callback = - proc () = - # TODO: Fix this in async procs. - raise newException(ReplyError, msg) - return retFuture - -proc checkReply(smtp: Smtp | AsyncSmtp, reply: string) {.multisync.} = - var line = await smtp.debugRecv() - if not line.startswith(reply): - await quitExcpt(smtp, "Expected " & reply & " reply, got: " & line) - -proc connect*(smtp: Smtp | AsyncSmtp, - address: string, port: Port) {.multisync.} = - ## Establishes a connection with a SMTP server. - ## May fail with ReplyError or with a socket error. - await smtp.sock.connect(address, port) - - await smtp.checkReply("220") - await smtp.debugSend("HELO " & address & "\c\L") - await smtp.checkReply("250") - -proc auth*(smtp: Smtp | AsyncSmtp, username, password: string) {.multisync.} = - ## Sends an AUTH command to the server to login as the `username` - ## using `password`. - ## May fail with ReplyError. - - await smtp.debugSend("AUTH LOGIN\c\L") - await smtp.checkReply("334") # TODO: Check whether it's asking for the "Username:" - # i.e "334 VXNlcm5hbWU6" - await smtp.debugSend(encode(username) & "\c\L") - await smtp.checkReply("334") # TODO: Same as above, only "Password:" (I think?) - - await smtp.debugSend(encode(password) & "\c\L") - await smtp.checkReply("235") # Check whether the authentification was successful. - -proc sendMail*(smtp: Smtp | AsyncSmtp, fromAddr: string, - toAddrs: seq[string], msg: string) {.multisync.} = - ## Sends ``msg`` from ``fromAddr`` to the addresses specified in ``toAddrs``. - ## Messages may be formed using ``createMessage`` by converting the - ## Message into a string. - - await smtp.debugSend("MAIL FROM:<" & fromAddr & ">\c\L") - await smtp.checkReply("250") - for address in items(toAddrs): - await smtp.debugSend("RCPT TO:<" & address & ">\c\L") - await smtp.checkReply("250") - - # Send the message - await smtp.debugSend("DATA " & "\c\L") - await smtp.checkReply("354") - await smtp.sock.send(msg & "\c\L") - await smtp.debugSend(".\c\L") - await smtp.checkReply("250") - -proc close*(smtp: Smtp | AsyncSmtp) {.multisync.} = - ## Disconnects from the SMTP server and closes the socket. - await smtp.debugSend("QUIT\c\L") - smtp.sock.close() - -when not defined(testing) and isMainModule: - # To test with a real SMTP service, create a smtp.ini file, e.g.: - # username = "" - # password = "" - # smtphost = "smtp.gmail.com" - # port = 465 - # use_tls = true - # sender = "" - # recipient = "" - - import parsecfg - - proc `[]`(c: Config, key: string): string = c.getSectionValue("", key) - - let - conf = loadConfig("smtp.ini") - msg = createMessage("Hello from Nim's SMTP!", - "Hello!\n Is this awesome or what?", @[conf["recipient"]]) - - assert conf["smtphost"] != "" - - proc async_test() {.async.} = - let client = newAsyncSmtp( - conf["use_tls"].parseBool, - debug=true - ) - await client.connect(conf["smtphost"], conf["port"].parseInt.Port) - await client.auth(conf["username"], conf["password"]) - await client.sendMail(conf["sender"], @[conf["recipient"]], $msg) - await client.close() - echo "async email sent" - - proc sync_test() = - var smtpConn = newSmtp( - conf["use_tls"].parseBool, - debug=true - ) - smtpConn.connect(conf["smtphost"], conf["port"].parseInt.Port) - smtpConn.auth(conf["username"], conf["password"]) - smtpConn.sendMail(conf["sender"], @[conf["recipient"]], $msg) - smtpConn.close() - echo "sync email sent" - - waitFor async_test() - sync_test() diff --git a/lib/pure/smtp.nim.cfg b/lib/pure/smtp.nim.cfg deleted file mode 100644 index 521e21de4..000000000 --- a/lib/pure/smtp.nim.cfg +++ /dev/null @@ -1 +0,0 @@ --d:ssl diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim index 9a502a256..32784e752 100644 --- a/tools/kochdocs.nim +++ b/tools/kochdocs.nim @@ -167,7 +167,6 @@ lib/impure/db_sqlite.nim lib/impure/db_odbc.nim lib/pure/db_common.nim lib/pure/httpclient.nim -lib/pure/smtp.nim lib/pure/ropes.nim lib/pure/unidecode/unidecode.nim lib/pure/xmlparser.nim -- cgit 1.4.1-2-gfad0 From be958d60c92bf052cc85ac80dd49b589edb95e64 Mon Sep 17 00:00:00 2001 From: narimiran Date: Tue, 8 Jan 2019 17:29:54 +0100 Subject: remove `oids` --- doc/lib.rst | 6 ---- lib/pure/oids.nim | 93 ------------------------------------------------------ tools/kochdocs.nim | 1 - 3 files changed, 100 deletions(-) delete mode 100644 lib/pure/oids.nim diff --git a/doc/lib.rst b/doc/lib.rst index fdc188ef2..d6c296e8d 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -372,12 +372,6 @@ Multimedia support Miscellaneous ------------- -* `oids `_ - An OID is a global ID that consists of a timestamp, - a unique counter and a random value. This combination should suffice to - produce a globally distributed unique ID. This implementation was extracted - from the Mongodb interface and it thus binary compatible with a Mongo OID. - * `endians `_ This module contains helpers that deal with different byte orders. diff --git a/lib/pure/oids.nim b/lib/pure/oids.nim deleted file mode 100644 index d6369b5f9..000000000 --- a/lib/pure/oids.nim +++ /dev/null @@ -1,93 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2013 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## Nim OID support. An OID is a global ID that consists of a timestamp, -## a unique counter and a random value. This combination should suffice to -## produce a globally distributed unique ID. This implementation was extracted -## from the Mongodb interface and it thus binary compatible with a Mongo OID. -## -## This implementation calls ``math.randomize()`` for the first call of -## ``genOid``. - -import times, endians - -type - Oid* = object ## an OID - time: int32 ## - fuzz: int32 ## - count: int32 ## - -proc `==`*(oid1: Oid, oid2: Oid): bool = - ## Compare two Mongo Object IDs for equality - return (oid1.time == oid2.time) and (oid1.fuzz == oid2.fuzz) and (oid1.count == oid2.count) - -proc hexbyte*(hex: char): int = - case hex - of '0'..'9': result = (ord(hex) - ord('0')) - of 'a'..'f': result = (ord(hex) - ord('a') + 10) - of 'A'..'F': result = (ord(hex) - ord('A') + 10) - else: discard - -proc parseOid*(str: cstring): Oid = - ## parses an OID. - var bytes = cast[cstring](addr(result.time)) - var i = 0 - while i < 12: - bytes[i] = chr((hexbyte(str[2 * i]) shl 4) or hexbyte(str[2 * i + 1])) - inc(i) - -proc oidToString*(oid: Oid, str: cstring) = - const hex = "0123456789abcdef" - # work around a compiler bug: - var str = str - var o = oid - var bytes = cast[cstring](addr(o)) - var i = 0 - while i < 12: - let b = bytes[i].ord - str[2 * i] = hex[(b and 0xF0) shr 4] - str[2 * i + 1] = hex[b and 0xF] - inc(i) - str[24] = '\0' - -proc `$`*(oid: Oid): string = - result = newString(24) - oidToString(oid, result) - -var - incr: int - fuzz: int32 - -proc genOid*(): Oid = - ## generates a new OID. - proc rand(): cint {.importc: "rand", header: "", nodecl.} - proc srand(seed: cint) {.importc: "srand", header: "", nodecl.} - - var t = getTime().toUnix.int32 - - var i = int32(atomicInc(incr)) - - if fuzz == 0: - # racy, but fine semantically: - srand(t) - fuzz = rand() - bigEndian32(addr result.time, addr(t)) - result.fuzz = fuzz - bigEndian32(addr result.count, addr(i)) - -proc generatedTime*(oid: Oid): Time = - ## returns the generated timestamp of the OID. - var tmp: int32 - var dummy = oid.time - bigEndian32(addr(tmp), addr(dummy)) - result = fromUnix(tmp) - -when not defined(testing) and isMainModule: - let xo = genOid() - echo xo.generatedTime diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim index 32784e752..f2b09d380 100644 --- a/tools/kochdocs.nim +++ b/tools/kochdocs.nim @@ -196,7 +196,6 @@ lib/pure/memfiles.nim lib/pure/collections/critbits.nim lib/core/locks.nim lib/core/rlocks.nim -lib/pure/oids.nim lib/pure/endians.nim lib/pure/uri.nim lib/pure/nimprof.nim -- cgit 1.4.1-2-gfad0 From 44b4e289d656475df5f0123de86ccc89e366edcb Mon Sep 17 00:00:00 2001 From: cooldome Date: Thu, 10 Jan 2019 08:25:35 +0000 Subject: destructors: lift type bound operations for case and distinct objects (#10238) --- compiler/semasgn.nim | 46 +++++++++++++++++++++++--- tests/destructor/tdestructor.nim | 71 ++++++++++++++++++++++++++++------------ 2 files changed, 91 insertions(+), 26 deletions(-) diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim index 5d676dc76..c1ecaf8a2 100644 --- a/compiler/semasgn.nim +++ b/compiler/semasgn.nim @@ -49,6 +49,14 @@ proc liftBodyObj(c: var TLiftCtx; n, body, x, y: PNode) = liftBodyAux(c, f.typ, body, x.dotField(f), y.dotField(f)) of nkNilLit: discard of nkRecCase: + if c.kind in {attachedSink, attachedAsgn, attachedDeepCopy}: + ## the value needs to be destroyed before we assign the selector + ## or the value is lost + let prevKind = c.kind + c.kind = attachedDestructor + liftBodyObj(c, n, body, x, y) + c.kind = prevKind + # copy the selector: liftBodyObj(c, n[0], body, x, y) # we need to generate a case statement: @@ -66,7 +74,6 @@ proc liftBodyObj(c: var TLiftCtx; n, body, x, y: PNode) = liftBodyObj(c, n[i].lastSon, branch.sons[L-1], x, y) caseStmt.add(branch) body.add(caseStmt) - localError(c.c.config, c.info, "cannot lift assignment operator to 'case' object") of nkRecList: for t in items(n): liftBodyObj(c, t, body, x, y) else: @@ -235,11 +242,12 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) = discard considerOverloadedOp(c, t, body, x, y) else: defaultOp(c, t, body, x, y) - of tyObject, tyDistinct: + of tyObject: + if not considerOverloadedOp(c, t, body, x, y): + liftBodyObj(c, t.n, body, x, y) + of tyDistinct: if not considerOverloadedOp(c, t, body, x, y): - if t.sons[0] != nil: - liftBodyAux(c, t.sons[0].skipTypes(skipPtrs), body, x, y) - if t.kind == tyObject: liftBodyObj(c, t.n, body, x, y) + liftBodyAux(c, t.sons[0].skipTypes(skipPtrs), body, x, y) of tyTuple: liftBodyTup(c, t, body, x, y) of tyProc: @@ -279,8 +287,36 @@ proc addParam(procType: PType; param: PSym) = addSon(procType.n, newSymNode(param)) rawAddSon(procType, param.typ) +proc liftBodyDistinctType(c: PContext; typ: PType; kind: TTypeAttachedOp; info: TLineInfo): PSym = + assert typ.kind == tyDistinct + let baseType = typ[0] + case kind + of attachedAsgn: + if baseType.assignment == nil: + discard liftBody(c, baseType, kind, info) + typ.assignment = baseType.assignment + result = typ.assignment + of attachedSink: + if baseType.sink == nil: + discard liftBody(c, baseType, kind, info) + typ.sink = baseType.sink + result = typ.sink + of attachedDeepCopy: + if baseType.deepCopy == nil: + discard liftBody(c, baseType, kind, info) + typ.deepCopy = baseType.deepCopy + result = typ.deepCopy + of attachedDestructor: + if baseType.destructor == nil: + discard liftBody(c, baseType, kind, info) + typ.destructor = baseType.destructor + result = typ.destructor + proc liftBody(c: PContext; typ: PType; kind: TTypeAttachedOp; info: TLineInfo): PSym = + if typ.kind == tyDistinct: + return liftBodyDistinctType(c, typ, kind, info) + var a: TLiftCtx a.info = info a.c = c diff --git a/tests/destructor/tdestructor.nim b/tests/destructor/tdestructor.nim index c9f1caf2d..09dce19ab 100644 --- a/tests/destructor/tdestructor.nim +++ b/tests/destructor/tdestructor.nim @@ -7,21 +7,28 @@ mygeneric1 constructed mygeneric1 destroyed ---- mygeneric2 constructed -mygeneric2 destroyed myobj destroyed +mygeneric2 destroyed ---- mygeneric3 constructed mygeneric1 destroyed ---- -mygeneric1 destroyed ----- +mydistinctObj constructed myobj destroyed +mygeneric2 destroyed +------------------ ---- ---- myobj destroyed +mygeneric1 destroyed +myobj destroyed +myobj destroyed +myobj destroyed +--- +myobj destroyed +myobj destroyed +myobj destroyed ''' - cmd: '''nim c --newruntime $file''' - disabled: "true" """ type @@ -29,6 +36,11 @@ type x, y: int p: pointer +proc `=destroy`(o: var TMyObj) = + if o.p != nil: dealloc o.p + echo "myobj destroyed" + +type TMyGeneric1[T] = object x: T @@ -36,37 +48,40 @@ type x: A y: B +proc `=destroy`(o: var TMyGeneric1[int]) = + echo "mygeneric1 destroyed" + +proc `=destroy`[A, B](o: var TMyGeneric2[A, B]) = + echo "mygeneric2 destroyed" + +type TMyGeneric3[A, B, C] = object x: A y: B z: C - TObjKind = enum A, B, C, D + TDistinctObjX = distinct TMyGeneric3[TMyObj, TMyGeneric2[int, int], int] + TDistinctObj = TDistinctObjX + + TObjKind = enum Z, A, B, C, D TCaseObj = object + z: TMyGeneric3[TMyObj, float, int] case kind: TObjKind + of Z: discard of A: x: TMyGeneric1[int] of B, C: y: TMyObj else: case innerKind: TObjKind + of Z: discard of A, B, C: p: TMyGeneric3[int, float, string] of D: q: TMyGeneric3[TMyObj, int, int] r: string -proc `=destroy`(o: var TMyObj) = - if o.p != nil: dealloc o.p - echo "myobj destroyed" - -proc `=destroy`(o: var TMyGeneric1[int]) = - echo "mygeneric1 destroyed" - -proc `=destroy`[A, B](o: var TMyGeneric2[A, B]) = - echo "mygeneric2 destroyed" - proc open: TMyObj = # allow for superfluous () result = (TMyObj(x: 1, y: 2, p: alloc(3))) @@ -95,6 +110,12 @@ proc mygeneric3 = echo "mygeneric3 constructed" +proc mydistinctObj = + var x = TMyGeneric3[TMyObj, TMyGeneric2[int, int], int]( + x: open(), y: TMyGeneric2[int, int](x: 5, y: 15), z: 20) + + echo "mydistinctObj constructed" + echo "----" myobj() @@ -107,9 +128,11 @@ mygeneric2[int](10) echo "----" mygeneric3() +echo "----" +mydistinctObj() + proc caseobj = block: - echo "----" var o1 = TCaseObj(kind: A, x: TMyGeneric1[int](x: 10)) block: @@ -121,10 +144,16 @@ proc caseobj = var o3 = TCaseObj(kind: D, innerKind: B, r: "test", p: TMyGeneric3[int, float, string](x: 10, y: 1.0, z: "test")) - block: - echo "----" - var o4 = TCaseObj(kind: D, innerKind: D, r: "test", - q: TMyGeneric3[TMyObj, int, int](x: open(), y: 1, z: 0)) +echo "------------------" caseobj() +proc caseobj_test_sink: TCaseObj = + # check that lifted sink can destroy case val correctly + result = TCaseObj(kind: D, innerKind: D, r: "test", + q: TMyGeneric3[TMyObj, int, int](x: open(), y: 1, z: 0)) + result = TCaseObj(kind: B, y: open()) + + +echo "---" +discard caseobj_test_sink() \ No newline at end of file -- cgit 1.4.1-2-gfad0 From f55c8d4d5430b9bf35a5a1ca8e8fe678c52a963b Mon Sep 17 00:00:00 2001 From: Arne Döring Date: Thu, 10 Jan 2019 09:27:01 +0100 Subject: fixes #10251 (#10255) --- compiler/lookups.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/lookups.nim b/compiler/lookups.nim index 269447486..8bc263485 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -190,7 +190,7 @@ proc addDecl*(c: PContext, sym: PSym, info: TLineInfo) = wrongRedefinition(c, info, sym.name.s, conflict.info) proc addDecl*(c: PContext, sym: PSym) = - let conflict = c.currentScope.addUniqueSym(sym) + let conflict = strTableInclReportConflict(c.currentScope.symbols, sym, true) if conflict != nil: wrongRedefinition(c, sym.info, sym.name.s, conflict.info) -- cgit 1.4.1-2-gfad0 From 3ed5f83704f575c56dfd44a43535966a9b65c025 Mon Sep 17 00:00:00 2001 From: Oscar Nihlgård Date: Thu, 10 Jan 2019 09:28:09 +0100 Subject: Fix exception tracking for system.open (#10253) --- lib/system.nim | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/system.nim b/lib/system.nim index 8a35714a5..dff195402 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -3237,14 +3237,15 @@ when not defined(JS): #and not defined(nimscript): proc open*(f: var File, filename: string, mode: FileMode = fmRead, bufSize: int = -1): bool {.tags: [], - benign.} + raises: [], benign.} ## Opens a file named `filename` with given `mode`. ## ## Default mode is readonly. Returns true iff the file could be opened. ## This throws no exception if the file could not be opened. proc open*(f: var File, filehandle: FileHandle, - mode: FileMode = fmRead): bool {.tags: [], benign.} + mode: FileMode = fmRead): bool {.tags: [], raises: [], + benign.} ## Creates a ``File`` from a `filehandle` with given `mode`. ## ## Default mode is readonly. Returns true iff the file could be opened. @@ -3253,7 +3254,7 @@ when not defined(JS): #and not defined(nimscript): mode: FileMode = fmRead, bufSize: int = -1): File = ## Opens a file named `filename` with given `mode`. ## - ## Default mode is readonly. Raises an ``IO`` exception if the file + ## Default mode is readonly. Raises an ``IOError`` if the file ## could not be opened. if not open(result, filename, mode, bufSize): sysFatal(IOError, "cannot open: ", filename) -- cgit 1.4.1-2-gfad0 From f7d2f9c5f0844af8831d780ad2b289305c482ada Mon Sep 17 00:00:00 2001 From: narimiran Date: Thu, 10 Jan 2019 09:31:18 +0100 Subject: update changelog --- changelog.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index 87885aec9..da06f3daa 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,5 @@ -## v0.20.0 - XX/XX/2018 +## v0.20.0 - XX/XX/2019 + ### Changes affecting backwards compatibility @@ -26,6 +27,7 @@ - `getImpl` on a `var` or `let` symbol will now return the full `IdentDefs` tree from the symbol declaration instead of just the initializer portion. + #### Breaking changes in the standard library - `osproc.execProcess` now also takes a `workingDir` parameter. @@ -57,6 +59,13 @@ - `isNil` is no longer false for undefined in the JavaScript backend: now it's true for both nil and undefined. Use `isNull` or `isUndefined` if you need exact equallity: `isNil` is consistent with `===`, `isNull` and `isUndefined` with `==`. +- several deprecated modules were removed: `ssl`, `matchers`, `httpserver`, + `unsigned`, `actors`, `parseurl` + +- several poorly documented and not used modules were moved to graveyard + (they are available as Nimble packages): `subexes`, `scgi`, `smtp`, `oids` + + #### Breaking changes in the compiler - The compiler now implements the "generic symbol prepass" for `when` statements @@ -97,13 +106,16 @@ proc enumToString*(enums: openArray[enum]): string = is instantiation of generic proc symbol. - Added the parameter ``isSorted`` for the ``sequtils.deduplicate`` proc. + - There is a new stdlib module `std/diff` to compute the famous "diff" of two texts by line. - Added `os.relativePath`. + - Added `parseopt.remainingArgs`. + - Added `os.getCurrentCompilerExe` (implmented as `getAppFilename` at CT), - can be used to retrive the currently executing compiler. + can be used to retrieve the currently executing compiler. ### Library changes @@ -128,10 +140,12 @@ proc enumToString*(enums: openArray[enum]): string = - `os.joinPath` and `os.normalizePath` handle edge cases like ``"a/b/../../.."`` differently. +- `securehash` is moved to `lib/deprecated` + ### Language additions -- Vm suport for float32<->int32 and float64<->int64 casts was added. +- Vm support for float32<->int32 and float64<->int64 casts was added. - There is a new pragma block `noSideEffect` that works like the `gcsafe` pragma block. - added os.getCurrentProcessId() @@ -140,6 +154,7 @@ proc enumToString*(enums: openArray[enum]): string = pragmas for further analysis by macros - Custom pragmas are now supported for `var` and `let` symbols. + ### Language changes - The standard extension for SCF (source code filters) files was changed from @@ -161,7 +176,9 @@ proc enumToString*(enums: openArray[enum]): string = - `nimpretty`: --backup now defaults to `off` instead of `on` and the flag was un-documented; use `git` instead of relying on backup files. + ### Compiler changes - The deprecated `fmod` proc is now unavailable on the VM'. + ### Bugfixes -- cgit 1.4.1-2-gfad0 From d2b95cb476f3b232862c2e4d4fe6fb67daa01988 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Thu, 10 Jan 2019 00:35:37 -0800 Subject: fixes #10039 : CI now runs buildTools (eg, nimfind wasn't being compiled before); refactoring (#10242) --- koch.nim | 56 +++++++++++++++++++++++++++++++++--------------------- tools/kochdocs.nim | 6 ------ 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/koch.nim b/koch.nim index afdce3083..845dffada 100644 --- a/koch.nim +++ b/koch.nim @@ -26,7 +26,6 @@ import import tools / kochdocs const VersionAsString = system.NimVersion -const env_NIM_COMPILE_TO_CPP = "NIM_COMPILE_TO_CPP" const HelpText = """ @@ -47,6 +46,8 @@ Possible Commands: boot [options] bootstraps with given command line options distrohelper [bindir] helper for distro packagers tools builds Nim related tools + toolsNoNimble builds Nim related tools (except nimble) + doesn't require network connectivity nimble builds the Nimble tool Boot options: -d:release produce a release version of the compiler @@ -73,6 +74,11 @@ Web options: build the official docs, use UA-48159761-1 """ +let kochExe* = os.getAppFilename() + +proc kochExec*(cmd: string) = + exec kochExe.quoteShell & " " & cmd + template withDir(dir, body) = let old = getCurrentDir() try: @@ -123,9 +129,6 @@ proc bundleNimbleExe(latest: bool) = # installer.ini expects it under $nim/bin nimCompile("dist/nimble/src/nimble.nim", options = "-d:release --nilseqs:on") -proc buildNimfind() = - nimCompile("tools/nimfind.nim", options = "-d:release") - proc buildNimble(latest: bool) = # old installations created nim/nimblepkg/*.nim files. We remove these # here so that it cannot cause problems (nimble bug #306): @@ -199,13 +202,12 @@ proc buildTool(toolname, args: string) = nimexec("cc $# $#" % [args, toolname]) copyFile(dest="bin" / splitFile(toolname).name.exe, source=toolname.exe) -proc buildTools(latest: bool) = +proc buildTools() = bundleNimsuggest() nimCompile("tools/nimgrep.nim", options = "-d:release") when defined(windows): buildVccTool() nimCompile("nimpretty/nimpretty.nim", options = "-d:release") - buildNimble(latest) - buildNimfind() + nimCompile("tools/nimfind.nim", options = "-d:release") proc nsis(latest: bool; args: string) = bundleNimbleExe(latest) @@ -274,7 +276,7 @@ proc boot(args: string) = var output = "compiler" / "nim".exe var finalDest = "bin" / "nim".exe # default to use the 'c' command: - let defaultCommand = if getEnv(env_NIM_COMPILE_TO_CPP, "false") == "true": "cpp" else: "c" + let defaultCommand = if getEnv("NIM_COMPILE_TO_CPP", "false") == "true": "cpp" else: "c" let bootOptions = if args.len == 0 or args.startsWith("-"): defaultCommand else: "" echo "boot: defaultCommand: ", defaultCommand, " bootOptions: ", bootOptions let smartNimcache = (if "release" in args: "nimcache/r_" else: "nimcache/d_") & @@ -440,32 +442,38 @@ proc runCI(cmd: string) = # todo: implement `execWithEnv` exec("env NIM_COMPILE_TO_CPP=false $1 boot" % kochExe.quoteShell) kochExec "boot -d:release" + + ## build nimble early on to enable remainder to depend on it if needed kochExec "nimble" - exec "nim e tests/test_nimscript.nims" when false: for pkg in "zip opengl sdl1 jester@#head niminst".split: exec "nimble install -y" & pkg + buildTools() # altenatively, kochExec "tools --toolsNoNimble" + + ## run tests + exec "nim e tests/test_nimscript.nims" when defined(windows): # note: will be over-written below exec "nim c -d:nimCoroutines --os:genode -d:posix --compileOnly testament/tester" - when false: - kochExec "csource" - kochExec "zip" - # main bottleneck: runs all main tests + # main bottleneck here exec "nim c -r -d:nimCoroutines testament/tester --pedantic all -d:nimCoroutines" - exec "nim c -r nimdoc/tester" - nimCompile "nimpretty/nimpretty.nim" + exec "nim c -r nimdoc/tester" exec "nim c -r nimpretty/tester.nim" + when defined(posix): + exec "nim c -r nimsuggest/tester" + ## remaining actions when defined(posix): kochExec "docs --git.commit:devel" kochExec "csource" - kochExec "nimsuggest" - exec "nim c -r nimsuggest/tester" + elif defined(windows): + when false: + kochExec "csource" + kochExec "zip" proc pushCsources() = if not dirExists("../csources/.git"): @@ -550,6 +558,10 @@ when isMainModule: var op = initOptParser() var latest = false var stable = false + template isLatest(): bool = + if stable: false + else: + existsDir(".git") or latest while true: op.next() case op.kind @@ -580,13 +592,13 @@ when isMainModule: of "temp": temp(op.cmdLineRest) of "xtemp": xtemp(op.cmdLineRest) of "wintools": bundleWinTools() - of "nimble": - if stable: buildNimble(false) - else: buildNimble(existsDir(".git") or latest) + of "nimble": buildNimble(isLatest()) of "nimsuggest": bundleNimsuggest() + of "toolsnonimble": + buildTools() of "tools": - if stable: buildTools(false) - else: buildTools(existsDir(".git") or latest) + buildTools() + buildNimble(isLatest()) of "pushcsource", "pushcsources": pushCsources() of "valgrind": valgrind(op.cmdLineRest) else: showHelp() diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim index 19b0b3d78..6741fcf5d 100644 --- a/tools/kochdocs.nim +++ b/tools/kochdocs.nim @@ -51,12 +51,6 @@ proc execCleanPath*(cmd: string, if execShellCmd(cmd) != 0: quit("FAILURE", errorcode) putEnv("PATH", prevPath) -let kochExe* = os.getAppFilename() - # note: assumes `kochdocs` is only used by koch.nim - -proc kochExec*(cmd: string) = - exec kochExe.quoteShell & " " & cmd - proc nimexec*(cmd: string) = # Consider using `nimCompile` instead exec findNim() & " " & cmd -- cgit 1.4.1-2-gfad0 From b3435d22dcb262371319f045aacb3a5d80d7f8aa Mon Sep 17 00:00:00 2001 From: Oscar Nihlgård Date: Thu, 10 Jan 2019 10:56:12 +0100 Subject: Times cosmetic changes (#10237) * Add more Date wrappers to jscore * Times cosmetic changes - Improved docs - Code wrapped at 80 chars - Formatting fixes using nimpretty - Remove some old deprecated procs --- lib/js/jscore.nim | 12 +- lib/pure/times.nim | 703 ++++++++++++++++++++++++++++------------------------- 2 files changed, 385 insertions(+), 330 deletions(-) diff --git a/lib/js/jscore.nim b/lib/js/jscore.nim index bf64b0794..2e2bd2402 100644 --- a/lib/js/jscore.nim +++ b/lib/js/jscore.nim @@ -73,7 +73,7 @@ proc parse*(d: DateLib, s: cstring): int {.importcpp.} proc newDate*(): DateTime {. importcpp: "new Date()".} -proc newDate*(date: int|string): DateTime {. +proc newDate*(date: int|int64|string): DateTime {. importcpp: "new Date(#)".} proc newDate*(year, month, day, hours, minutes, @@ -90,6 +90,16 @@ proc getSeconds*(d: DateTime): int {.importcpp.} proc getYear*(d: DateTime): int {.importcpp.} proc getTime*(d: DateTime): int {.importcpp.} proc toString*(d: DateTime): cstring {.importcpp.} +proc getUTCDate*(d: DateTime): int {.importcpp.} +proc getUTCFullYear*(d: DateTime): int {.importcpp.} +proc getUTCHours*(d: DateTime): int {.importcpp.} +proc getUTCMilliseconds*(d: DateTime): int {.importcpp.} +proc getUTCMinutes*(d: DateTime): int {.importcpp.} +proc getUTCMonth*(d: DateTime): int {.importcpp.} +proc getUTCSeconds*(d: DateTime): int {.importcpp.} +proc getUTCDay*(d: DateTime): int {.importcpp.} +proc getTimezoneOffset*(d: DateTime): int {.importcpp.} +proc setFullYear*(d: DateTime, year: int) {.importcpp.} #JSON library proc stringify*(l: JsonLib, s: JsRoot): cstring {.importcpp.} diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 0a06d5f9f..8fe04a4b9 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1,35 +1,41 @@ # # # Nim's Runtime Library -# (c) Copyright 2017 Nim contributors +# (c) Copyright 2018 Nim contributors # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ##[ - This module contains routines and types for dealing with time using a proleptic Gregorian calendar. - It's also available for the `JavaScript target `_. + The ``times`` module contains routines and types for dealing with time using + the `proleptic Gregorian calendar`_. + It's also available for the + `JavaScript target `_. - Although the types use nanosecond time resolution, the underlying resolution used by ``getTime()`` - depends on the platform and backend (JS is limited to millisecond precision). + Although the ``times`` module support nanosecond time resolution, the + resolution used by ``getTime()`` depends on the platform and backend + (JS is limited to millisecond precision). Examples: .. code-block:: nim - import times, os + # Simple benchmarking let time = cpuTime() - - sleep(100) # replace this with something to be timed + sleep(100) # Replace this with something to be timed echo "Time taken: ", cpuTime() - time - echo "My formatted time: ", format(now(), "d MMMM yyyy HH:mm") - echo "Using predefined formats: ", getClockStr(), " ", getDateStr() + # Current date & time + let now1 = now() # Current timestamp as a DateTime in local time + let now2 = now().utc # Current timestamp as a DateTime in UTC + let now3 = getTime() # Current timestamp as a Time - echo "cpuTime() float value: ", cpuTime() - echo "An hour from now : ", now() + 1.hours - echo "An hour from (UTC) now: ", getTime().utc + initDuration(hours = 1) + # Arithmetic using Duration + echo "One hour from now : ", now() + initDuration(hours = 1) + # Arithmetic using TimeInterval + echo "One year from now : ", now() + 1.years + echo "One month from now : ", now() + 1.months Parsing and Formatting Dates ---------------------------- @@ -97,14 +103,14 @@ | ``24 AD -> 24`` | ``24 BC -> -23`` | ``12345 AD -> 12345`` - ``z`` Displays the timezone offset from UTC. | ``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`` - ``zzzz`` Same as above but with ``:ss`` where *ss* represents seconds. | ``GMT+7 -> +07:00:00`` - | ``GMT-5 -> -05:00:00`` + ``z`` Displays the timezone offset from UTC. | ``UTC+7 -> +7`` + | ``UTC-5 -> -5`` + ``zz`` Same as above but with leading 0. | ``UTC+7 -> +07`` + | ``UTC-5 -> -05`` + ``zzz`` Same as above but with ``:mm`` where *mm* represents minutes. | ``UTC+7 -> +07:00`` + | ``UTC-5 -> -05:00`` + ``zzzz`` Same as above but with ``:ss`` where *ss* represents seconds. | ``UTC+7 -> +07:00:00`` + | ``UTC-5 -> -05:00:00`` ``g`` Era: AD or BC | ``300 AD -> AD`` | ``300 BC -> BC`` ``fff`` Milliseconds display | ``1000000 nanoseconds -> 1`` @@ -117,49 +123,109 @@ inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` ``,``. A literal ``'`` can be specified with ``''``. - However you don't need to necessarily separate format patterns, a + However you don't need to necessarily separate format patterns, an unambiguous format string like ``yyyyMMddhhmmss`` is valid too (although only for years in the range 1..9999). + + Duration vs TimeInterval + ---------------------------- + The ``times`` module exports two similiar types that are both used to + represent some amount of time: ``Duration`` and ``TimeInterval``. + This section explains how they differ and when one should be prefered over the + other (short answer: use ``Duration`` unless support for months and years is + needed). + + Duration + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A ``Duration`` represents a duration of time stored as seconds and + nanoseconds. A ``Duration`` is always fully normalized, so +``initDuration(hours = 1)`` and ``initDuration(minutes = 60)`` are equivilant. + + Arithmetics with a ``Duration`` is very fast, especially when used with the + ``Time`` type, since it only involves basic arithmetic. Because ``Duration`` + is more performant and easier to understand it should generally prefered. + + TimeInterval + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A ``TimeInterval`` represents some amount of time expressed in calendar + units, for example "1 year and 2 days". Since some units cannot be + normalized (the length of a year is different for leap years for example), + the ``TimeInterval`` type uses seperate fields for every unit. The + ``TimeInterval``'s returned form the this module generally don't normalize + **anything**, so even units that could be normalized (like seconds, + milliseconds and so on) are left untouched. + + Arithmetics with a ``TimeInterval`` can be very slow, because it requires + timezone information. + + Since it's slower and more complex, the ``TimeInterval`` type should be + avoided unless the program explicitly needs the features it offers that + ``Duration`` doesn't have. + + How long is a day? + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + It should be especially noted that the handling of days differs between + ``TimeInterval`` and ``Duration``. The ``Duration`` type always treats a day + as exactly 86400 seconds. For ``TimeInterval``, it's more complex. + + As an example, consider the amount of time between these two timestamps, both + in the same timezone: + + - 2018-03-25T12:00+02:00 + - 2018-03-26T12:00+01:00 + + If only the date & time is considered, it appears that exatly one day has + passed. However, the UTC offsets are different, which means that the + UTC offset was changed somewhere between. This happens twice each year for + timezones that use daylight savings time. Because of this change, the amount + of time that has passed is actually 25 hours. + + The ``TimeInterval`` type uses calendar units, and will say that exactly one + day has passed. The ``Duration`` type on the other hand normalizes everything + to seconds, and will therefore say that 90000 seconds has passed, which is + the same as 25 hours. ]## -import - strutils, algorithm, math, options, strformat +import strutils, algorithm, math, options, strformat include "system/inclrtl" -# This is really bad, but overflow checks are broken badly for -# ints on the JS backend. See #6752. when defined(JS): + import jscore + + # This is really bad, but overflow checks are broken badly for + # ints on the JS backend. See #6752. {.push overflowChecks: off.} proc `*`(a, b: int64): int64 = - system.`* `(a, b) + system.`*`(a, b) proc `*`(a, b: int): int = - system.`* `(a, b) + system.`*`(a, b) proc `+`(a, b: int64): int64 = - system.`+ `(a, b) + system.`+`(a, b) proc `+`(a, b: int): int = - system.`+ `(a, b) + system.`+`(a, b) proc `-`(a, b: int64): int64 = - system.`- `(a, b) + system.`-`(a, b) proc `-`(a, b: int): int = - system.`- `(a, b) + system.`-`(a, b) proc inc(a: var int, b: int) = system.inc(a, b) proc inc(a: var int64, b: int) = system.inc(a, b) {.pop.} -when defined(posix): +elif defined(posix): import posix type CTime = posix.Time var realTimeClockId {.importc: "CLOCK_REALTIME", header: "".}: Clockid - cpuClockId {.importc: "CLOCK_THREAD_CPUTIME_ID", header: "".}: Clockid + cpuClockId + {.importc: "CLOCK_THREAD_CPUTIME_ID", header: "".}: Clockid - proc gettimeofday(tp: var Timeval, unused: pointer = nil) {. - importc: "gettimeofday", header: "".} + proc gettimeofday(tp: var Timeval, unused: pointer = nil) + {.importc: "gettimeofday", header: "".} when not defined(freebsd) and not defined(netbsd) and not defined(openbsd): var timezone {.importc, header: "".}: int @@ -177,9 +243,24 @@ elif defined(windows): # visual c's c runtime exposes these under a different name var timezone {.importc: "_timezone", header: "".}: int + type + Tm {.importc: "struct tm", header: "", final, pure.} = object + tm_sec*: cint ## Seconds [0,60]. + tm_min*: cint ## Minutes [0,59]. + tm_hour*: cint ## Hour [0,23]. + tm_mday*: cint ## Day of month [1,31]. + tm_mon*: cint ## Month of year [0,11]. + tm_year*: cint ## Years since 1900. + tm_wday*: cint ## Day of week [0,6] (Sunday =0). + tm_yday*: cint ## Day of year [0,365]. + tm_isdst*: cint ## Daylight Savings flag. + + proc localtime(a1: var CTime): ptr Tm {.importc, header: "".} + type - Month* = enum ## Represents a month. Note that the enum starts at ``1``, so ``ord(month)`` will give - ## the month number in the range ``[1..12]``. + Month* = enum ## Represents a month. Note that the enum starts at ``1``, + ## so ``ord(month)`` will give the month number in the + ## range ``1..12``. mJan = (1, "January") mFeb = "February" mMar = "March" @@ -213,13 +294,19 @@ type seconds: int64 nanosecond: NanosecondRange - DateTime* = object of RootObj ## Represents a time in different parts. - ## Although this type can represent leap - ## seconds, they are generally not supported - ## in this module. They are not ignored, - ## but the ``DateTime``'s returned by - ## procedures in this module will never have - ## a leap second. + DateTime* = object of RootObj ## \ + ## Represents a time in different parts. Although this type can represent + ## leap seconds, they are generally not supported in this module. They are + ## not ignored, but the ``DateTime``'s returned by procedures in this + ## module will never have a leap second. + ## + ## **Warning**: even though the fields of ``DateTime`` are exported, + ## they should never be mutated directly. Doing so is unsafe and will + ## result in the ``DateTime`` ending up in an invalid state. + ## + ## Instead of mutating the fields directly, use the ``Duration`` + ## and ``TimeInterval`` types for arithmetic and use the ``initDateTime`` + ## procedure for changing a specific field. nanosecond*: NanosecondRange ## The number of nanoseconds after the second, ## in the range 0 to 999_999_999. second*: SecondRange ## The number of seconds after the minute, @@ -230,27 +317,48 @@ type hour*: HourRange ## The number of hours past midnight, ## in the range 0 to 23. monthday*: MonthdayRange ## The day of the month, in the range 1 to 31. - month*: Month ## The current month. - year*: int ## The current year, using astronomical year numbering - ## (meaning that before year 1 is year 0, then year -1 and so on). - weekday*: WeekDay ## The current day of the week. + month*: Month ## The month. + year*: int ## The year, using astronomical year numbering + ## (meaning that before year 1 is year 0, + ## then year -1 and so on). + weekday*: WeekDay ## The day of the week. yearday*: YeardayRange ## The number of days since January 1, ## in the range 0 to 365. isDst*: bool ## Determines whether DST is in effect. ## Always false for the JavaScript backend. - timezone*: Timezone ## The timezone represented as an implementation of ``Timezone``. - utcOffset*: int ## The offset in seconds west of UTC, including any offset due to DST. - ## Note that the sign of this number is the opposite - ## of the one in a formatted offset string like ``+01:00`` - ## (which would be parsed into the UTC offset ``-3600``). - - TimeInterval* = object ## Represents a non-fixed duration of time. Can be used to add and subtract - ## non-fixed time units from a ``DateTime`` or ``Time``. - ## ``TimeInterval`` doesn't represent a fixed duration of time, - ## since the duration of some units depend on the context (e.g a year - ## can be either 365 or 366 days long). The non-fixed time units are years, - ## months and days. + timezone*: Timezone ## The timezone represented as an implementation + ## of ``Timezone``. + utcOffset*: int ## The offset in seconds west of UTC, including + ## any offset due to DST. Note that the sign of + ## this number is the opposite of the one in a + ## formatted offset string like ``+01:00`` (which + ## would be equivalent to the UTC offset + ## ``-3600``). + + Duration* = object ## Represents a fixed duration of time, meaning a duration + ## that has constant length independent of the context. + seconds: int64 + nanosecond: NanosecondRange + TimeUnit* = enum ## Different units of time. + Nanoseconds, Microseconds, Milliseconds, Seconds, Minutes, Hours, Days, + Weeks, Months, Years + + FixedTimeUnit* = range[Nanoseconds..Weeks] ## \ + ## Subrange of ``TimeUnit`` that only includes units of fixed duration. + ## These are the units that can be represented by a ``Duration``. + + TimeInterval* = object ## \ + ## Represents a non-fixed duration of time. Can be used to add and + ## subtract non-fixed time units from a ``DateTime`` or ``Time``. + ## Note that ``TimeInterval`` doesn't represent a fixed duration of time, + ## since the duration of some units depend on the context (e.g a year + ## can be either 365 or 366 days long). The non-fixed time units are + ## years, months, days and week. + ## + ## Note that ``TimeInterval``'s returned from the ``times`` module are + ## never normalized. If you want to normalize a time unit, ``Duration`` + ## should be used instead. nanoseconds*: int ## The number of nanoseconds microseconds*: int ## The number of microseconds milliseconds*: int ## The number of milliseconds @@ -262,19 +370,6 @@ type months*: int ## The number of months years*: int ## The number of years - Duration* = object ## Represents a fixed duration of time. - ## Uses the same time resolution as ``Time``. - ## This type should be prefered over ``TimeInterval`` unless - ## non-static time units is needed. - seconds: int64 - nanosecond: NanosecondRange - - TimeUnit* = enum ## Different units of time. - Nanoseconds, Microseconds, Milliseconds, Seconds, Minutes, Hours, Days, Weeks, Months, Years - - FixedTimeUnit* = range[Nanoseconds..Weeks] ## Subrange of ``TimeUnit`` that only includes units of fixed duration. - ## These are the units that can be represented by a ``Duration``. - Timezone* = ref object ## \ ## Timezone interface for supporting ``DateTime``'s of arbritary ## timezones. The ``times`` module only supplies implementations for the @@ -317,8 +412,10 @@ const unitWeights: array[FixedTimeUnit, int64] = [ 7 * secondsInDay * 1e9.int64, ] -proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): T {.inline.} = +proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): T + {.inline.} = ## Convert a quantity of some duration unit to another duration unit. + ## This proc only deals with integers, so the result might be truncated. runnableExamples: doAssert convert(Days, Hours, 2) == 48 doAssert convert(Days, Weeks, 13) == 1 # Truncated @@ -340,21 +437,41 @@ proc normalize[T: Duration|Time](seconds, nanoseconds: int64): T = result.nanosecond = nanosecond.int # Forward declarations -proc utcTzInfo(time: Time): ZonedTime {.tags: [], raises: [], benign .} -proc localZonedTimeFromTime(time: Time): ZonedTime {.tags: [], raises: [], benign .} -proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.tags: [], raises: [], benign .} +proc utcTzInfo(time: Time): ZonedTime + {.tags: [], raises: [], benign.} +proc localZonedTimeFromTime(time: Time): ZonedTime + {.tags: [], raises: [], benign.} +proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime + {.tags: [], raises: [], benign.} proc initTime*(unix: int64, nanosecond: NanosecondRange): Time - {.tags: [], raises: [], benign noSideEffect.} - -proc initDuration*(nanoseconds, microseconds, milliseconds, - seconds, minutes, hours, days, weeks: int64 = 0): Duration - {.tags: [], raises: [], benign noSideEffect.} + {.tags: [], raises: [], benign, noSideEffect.} proc nanosecond*(time: Time): NanosecondRange = ## Get the fractional part of a ``Time`` as the number ## of nanoseconds of the second. time.nanosecond +proc initDuration*(nanoseconds, microseconds, milliseconds, + seconds, minutes, hours, days, weeks: int64 = 0): Duration = + ## Create a new duration. + runnableExamples: + let dur = initDuration(seconds = 1, milliseconds = 1) + doAssert dur.milliseconds == 1 + doAssert dur.seconds == 1 + + let seconds = convert(Weeks, Seconds, weeks) + + convert(Days, Seconds, days) + + convert(Minutes, Seconds, minutes) + + convert(Hours, Seconds, hours) + + convert(Seconds, Seconds, seconds) + + convert(Milliseconds, Seconds, milliseconds) + + convert(Microseconds, Seconds, microseconds) + + convert(Nanoseconds, Seconds, nanoseconds) + let nanoseconds = (convert(Milliseconds, Nanoseconds, milliseconds mod 1000) + + convert(Microseconds, Nanoseconds, microseconds mod 1_000_000) + + nanoseconds mod 1_000_000_000).int + # Nanoseconds might be negative so we must normalize. + result = normalize[Duration](seconds, nanoseconds) proc weeks*(dur: Duration): int64 {.inline.} = ## Number of whole weeks represented by the duration. @@ -407,9 +524,10 @@ proc fractional*(dur: Duration): Duration {.inline.} = doAssert dur.fractional == initDuration(nanoseconds = 5) initDuration(nanoseconds = dur.nanosecond) - -proc fromUnix*(unix: int64): Time {.benign, tags: [], raises: [], noSideEffect.} = - ## Convert a unix timestamp (seconds since ``1970-01-01T00:00:00Z``) to a ``Time``. +proc fromUnix*(unix: int64): Time + {.benign, tags: [], raises: [], noSideEffect.} = + ## Convert a unix timestamp (seconds since ``1970-01-01T00:00:00Z``) + ## to a ``Time``. runnableExamples: doAssert $fromUnix(0).utc == "1970-01-01T00:00:00Z" initTime(unix, 0) @@ -421,15 +539,16 @@ proc toUnix*(t: Time): int64 {.benign, tags: [], raises: [], noSideEffect.} = t.seconds proc fromWinTime*(win: int64): Time = - ## Convert a Windows file time (100-nanosecond intervals since ``1601-01-01T00:00:00Z``) - ## to a ``Time``. + ## Convert a Windows file time (100-nanosecond intervals since + ## ``1601-01-01T00:00:00Z``) to a ``Time``. const hnsecsPerSec = convert(Seconds, Nanoseconds, 1) div 100 let nanos = floorMod(win, hnsecsPerSec) * 100 let seconds = floorDiv(win - epochDiff, hnsecsPerSec) result = initTime(seconds, nanos) proc toWinTime*(t: Time): int64 = - ## Convert ``t`` to a Windows file time (100-nanosecond intervals since ``1601-01-01T00:00:00Z``). + ## Convert ``t`` to a Windows file time (100-nanosecond intervals + ## since ``1601-01-01T00:00:00Z``). result = t.seconds * rateDiff + epochDiff + t.nanosecond div 100 proc isLeapYear*(year: int): bool = @@ -437,7 +556,7 @@ proc isLeapYear*(year: int): bool = year mod 4 == 0 and (year mod 100 != 0 or year mod 400 == 0) proc getDaysInMonth*(month: Month, year: int): int = - ## Get the number of days in a ``month`` of a ``year``. + ## Get the number of days in ``month`` of ``year``. # http://www.dispersiondesign.com/articles/time/number_of_days_in_a_month case month of mFeb: result = if isLeapYear(year): 29 else: 28 @@ -448,15 +567,18 @@ proc getDaysInYear*(year: int): int = ## Get the number of days in a ``year`` result = 365 + (if isLeapYear(year): 1 else: 0) -proc assertValidDate(monthday: MonthdayRange, month: Month, year: int) {.inline.} = +proc assertValidDate(monthday: MonthdayRange, month: Month, year: int) + {.inline.} = assert monthday <= getDaysInMonth(month, year), - $year & "-" & intToStr(ord(month), 2) & "-" & $monthday & " is not a valid date" + $year & "-" & intToStr(ord(month), 2) & "-" & $monthday & + " is not a valid date" proc toEpochDay(monthday: MonthdayRange, month: Month, year: int): int64 = ## Get the epoch day from a year/month/day date. - ## The epoch day is the number of days since 1970/01/01 (it might be negative). - assertValidDate monthday, month, year + ## The epoch day is the number of days since 1970/01/01 + ## (it might be negative). # Based on http://howardhinnant.github.io/date_algorithms.html + assertValidDate monthday, month, year var (y, m, d) = (year, ord(month), monthday.int) if m <= 2: y.dec @@ -467,9 +589,11 @@ proc toEpochDay(monthday: MonthdayRange, month: Month, year: int): int64 = let doe = yoe * 365 + yoe div 4 - yoe div 100 + doy return era * 146097 + doe - 719468 -proc fromEpochDay(epochday: int64): tuple[monthday: MonthdayRange, month: Month, year: int] = +proc fromEpochDay(epochday: int64): + tuple[monthday: MonthdayRange, month: Month, year: int] = ## Get the year/month/day date from a epoch day. - ## The epoch day is the number of days since 1970/01/01 (it might be negative). + ## The epoch day is the number of days since 1970/01/01 + ## (it might be negative). # Based on http://howardhinnant.github.io/date_algorithms.html var z = epochday z.inc 719468 @@ -483,19 +607,23 @@ proc fromEpochDay(epochday: int64): tuple[monthday: MonthdayRange, month: Month, let m = mp + (if mp < 10: 3 else: -9) return (d.MonthdayRange, m.Month, (y + ord(m <= 2)).int) -proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int): YeardayRange {.tags: [], raises: [], benign .} = +proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int): + YeardayRange {.tags: [], raises: [], benign.} = ## Returns the day of the year. ## Equivalent with ``initDateTime(monthday, month, year, 0, 0, 0).yearday``. assertValidDate monthday, month, year - const daysUntilMonth: array[Month, int] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] - const daysUntilMonthLeap: array[Month, int] = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335] + const daysUntilMonth: array[Month, int] = + [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] + const daysUntilMonthLeap: array[Month, int] = + [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335] if isLeapYear(year): result = daysUntilMonthLeap[month] + monthday - 1 else: result = daysUntilMonth[month] + monthday - 1 -proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay {.tags: [], raises: [], benign .} = +proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay + {.tags: [], raises: [], benign.} = ## Returns the day of the week enum from day, month and year. ## Equivalent with ``initDateTime(monthday, month, year, 0, 0, 0).weekday``. assertValidDate monthday, month, year @@ -507,8 +635,7 @@ proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay {. # so we must correct for the WeekDay type. result = if wd == 0: dSun else: WeekDay(wd - 1) - -{. pragma: operator, rtl, noSideEffect, benign .} +{.pragma: operator, rtl, noSideEffect, benign.} template subImpl[T: Duration|Time](a: Duration|Time, b: Duration|Time): T = normalize[T](a.seconds - b.seconds, a.nanosecond - b.nanosecond) @@ -526,28 +653,6 @@ template lqImpl(a: Duration|Time, b: Duration|Time): bool = template eqImpl(a: Duration|Time, b: Duration|Time): bool = a.seconds == b.seconds and a.nanosecond == b.nanosecond - -proc initDuration*(nanoseconds, microseconds, milliseconds, - seconds, minutes, hours, days, weeks: int64 = 0): Duration = - runnableExamples: - let dur = initDuration(seconds = 1, milliseconds = 1) - doAssert dur.milliseconds == 1 - doAssert dur.seconds == 1 - - let seconds = convert(Weeks, Seconds, weeks) + - convert(Days, Seconds, days) + - convert(Minutes, Seconds, minutes) + - convert(Hours, Seconds, hours) + - convert(Seconds, Seconds, seconds) + - convert(Milliseconds, Seconds, milliseconds) + - convert(Microseconds, Seconds, microseconds) + - convert(Nanoseconds, Seconds, nanoseconds) - let nanoseconds = (convert(Milliseconds, Nanoseconds, milliseconds mod 1000) + - convert(Microseconds, Nanoseconds, microseconds mod 1_000_000) + - nanoseconds mod 1_000_000_000).int - # Nanoseconds might be negative so we must normalize. - result = normalize[Duration](seconds, nanoseconds) - const DurationZero* = initDuration() ## \ ## Zero value for durations. Useful for comparisons. ## @@ -564,7 +669,7 @@ proc toParts*(dur: Duration): DurationParts = ## ## This procedure is useful for converting ``Duration`` values to strings. runnableExamples: - var dp = toParts(initDuration(weeks=2, days=1)) + var dp = toParts(initDuration(weeks = 2, days = 1)) doAssert dp[Days] == 1 doAssert dp[Weeks] == 2 dp = toParts(initDuration(days = -1)) @@ -616,12 +721,14 @@ proc humanizeParts(parts: seq[string]): string = result.add "and " & parts[high(parts)] proc `$`*(dur: Duration): string = - ## Human friendly string representation of ``Duration``. + ## Human friendly string representation of a ``Duration``. runnableExamples: doAssert $initDuration(seconds = 2) == "2 seconds" doAssert $initDuration(weeks = 1, days = 2) == "1 week and 2 days" - doAssert $initDuration(hours = 1, minutes = 2, seconds = 3) == "1 hour, 2 minutes, and 3 seconds" - doAssert $initDuration(milliseconds = -1500) == "-1 second and -500 milliseconds" + doAssert $initDuration(hours = 1, minutes = 2, seconds = 3) == + "1 hour, 2 minutes, and 3 seconds" + doAssert $initDuration(milliseconds = -1500) == + "-1 second and -500 milliseconds" var parts = newSeq[string]() var numParts = toParts(dur) @@ -659,7 +766,7 @@ proc `<`*(a, b: Duration): bool {.operator.} = ## Use ``abs(a) < abs(b)`` to compare the absolute ## duration. runnableExamples: - doAssert initDuration(seconds = 1) < initDuration(seconds = 2) + doAssert initDuration(seconds = 1) < initDuration(seconds = 2) doAssert initDuration(seconds = -2) < initDuration(seconds = 1) ltImpl(a, b) @@ -669,23 +776,25 @@ proc `<=`*(a, b: Duration): bool {.operator.} = proc `==`*(a, b: Duration): bool {.operator.} = eqImpl(a, b) -proc `*`*(a: int64, b: Duration): Duration {.operator} = +proc `*`*(a: int64, b: Duration): Duration {.operator.} = ## Multiply a duration by some scalar. runnableExamples: doAssert 5 * initDuration(seconds = 1) == initDuration(seconds = 5) normalize[Duration](a * b.seconds, a * b.nanosecond) -proc `*`*(a: Duration, b: int64): Duration {.operator} = +proc `*`*(a: Duration, b: int64): Duration {.operator.} = ## Multiply a duration by some scalar. runnableExamples: doAssert initDuration(seconds = 1) * 5 == initDuration(seconds = 5) b * a -proc `div`*(a: Duration, b: int64): Duration {.operator} = +proc `div`*(a: Duration, b: int64): Duration {.operator.} = ## Integer division for durations. runnableExamples: - doAssert initDuration(seconds = 3) div 2 == initDuration(milliseconds = 1500) - doAssert initDuration(nanoseconds = 3) div 2 == initDuration(nanoseconds = 1) + doAssert initDuration(seconds = 3) div 2 == + initDuration(milliseconds = 1500) + doAssert initDuration(nanoseconds = 3) div 2 == + initDuration(nanoseconds = 1) let carryOver = convert(Seconds, Nanoseconds, a.seconds mod b) normalize[Duration](a.seconds div b, (a.nanosecond + carryOver) div b) @@ -714,7 +823,7 @@ proc `<`*(a, b: Time): bool {.operator, extern: "ntLtTime".} = ## Returns true iff ``a < b``, that is iff a happened before b. ltImpl(a, b) -proc `<=` * (a, b: Time): bool {.operator, extern: "ntLeTime".} = +proc `<=`*(a, b: Time): bool {.operator, extern: "ntLeTime".} = ## Returns true iff ``a <= b``. lqImpl(a, b) @@ -783,8 +892,10 @@ proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime = proc newTimezone*( name: string, - zonedTimeFromTimeImpl: proc (time: Time): ZonedTime {.tags: [], raises: [], benign.}, - zonedTimeFromAdjTimeImpl: proc (adjTime: Time): ZonedTime {.tags: [], raises: [], benign.} + zonedTimeFromTimeImpl: proc (time: Time): ZonedTime + {.tags: [], raises: [], benign.}, + zonedTimeFromAdjTimeImpl: proc (adjTime: Time): ZonedTime + {.tags: [], raises: [], benign.} ): Timezone = ## Create a new ``Timezone``. ## @@ -847,11 +958,13 @@ proc `==`*(zone1, zone2: Timezone): bool = doAssert local() != utc() zone1.name == zone2.name -proc inZone*(time: Time, zone: Timezone): DateTime {.tags: [], raises: [], benign.} = +proc inZone*(time: Time, zone: Timezone): DateTime + {.tags: [], raises: [], benign.} = ## Convert ``time`` into a ``DateTime`` using ``zone`` as the timezone. result = initDateTime(zone.zonedTimeFromTime(time), zone) -proc inZone*(dt: DateTime, zone: Timezone): DateTime {.tags: [], raises: [], benign.} = +proc inZone*(dt: DateTime, zone: Timezone): DateTime + {.tags: [], raises: [], benign.} = ## Returns a ``DateTime`` representing the same point in time as ``dt`` but ## using ``zone`` as the timezone. dt.toTime.inZone(zone) @@ -865,94 +978,38 @@ proc toAdjTime(dt: DateTime): Time = result = initTime(seconds, dt.nanosecond) when defined(JS): - type JsDate = object - proc newDate(year, month, date, hours, minutes, seconds, milliseconds: int): JsDate {.tags: [], raises: [], importc: "new Date".} - proc newDate(): JsDate {.importc: "new Date".} - proc newDate(value: float): JsDate {.importc: "new Date".} - proc getTimezoneOffset(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getDay(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getFullYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getHours(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getMilliseconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getMinutes(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getMonth(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getSeconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getTime(js: JsDate): int {.tags: [], raises: [], noSideEffect, benign, importcpp.} - proc getDate(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCDate(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCFullYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCHours(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCMilliseconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCMinutes(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCMonth(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCSeconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCDay(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc setFullYear(js: JsDate, year: int): void {.tags: [], raises: [], benign, importcpp.} - - proc localZonedTimeFromTime(time: Time): ZonedTime = - let jsDate = newDate(time.seconds.float * 1000) - let offset = jsDate.getTimezoneOffset() * secondsInMin - result.time = time - result.utcOffset = offset - result.isDst = false - - proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime = - let utcDate = newDate(adjTime.seconds.float * 1000) - let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), utcDate.getUTCDate(), - utcDate.getUTCHours(), utcDate.getUTCMinutes(), utcDate.getUTCSeconds(), 0) - - # This is as dumb as it looks - JS doesn't support years in the range 0-99 in the constructor - # because they are assumed to be 19xx... - # Because JS doesn't support timezone history, it doesn't really matter in practice. - if utcDate.getUTCFullYear() in 0 .. 99: - localDate.setFullYear(utcDate.getUTCFullYear()) - - result.utcOffset = localDate.getTimezoneOffset() * secondsInMin - result.time = adjTime + initDuration(seconds = result.utcOffset) - result.isDst = false + proc localZonedTimeFromTime(time: Time): ZonedTime = + let jsDate = newDate(time.seconds * 1000) + let offset = jsDate.getTimezoneOffset() * secondsInMin + result.time = time + result.utcOffset = offset + result.isDst = false -else: - when defined(freebsd) or defined(netbsd) or defined(openbsd) or - defined(macosx): - type - StructTm {.importc: "struct tm".} = object - second {.importc: "tm_sec".}, - minute {.importc: "tm_min".}, - hour {.importc: "tm_hour".}, - monthday {.importc: "tm_mday".}, - month {.importc: "tm_mon".}, - year {.importc: "tm_year".}, - weekday {.importc: "tm_wday".}, - yearday {.importc: "tm_yday".}, - isdst {.importc: "tm_isdst".}: cint - gmtoff {.importc: "tm_gmtoff".}: clong - else: - type - StructTm {.importc: "struct tm".} = object - second {.importc: "tm_sec".}, - minute {.importc: "tm_min".}, - hour {.importc: "tm_hour".}, - monthday {.importc: "tm_mday".}, - month {.importc: "tm_mon".}, - year {.importc: "tm_year".}, - weekday {.importc: "tm_wday".}, - yearday {.importc: "tm_yday".}, - isdst {.importc: "tm_isdst".}: cint - when defined(linux) and defined(amd64) or defined(haiku): - gmtoff {.importc: "tm_gmtoff".}: clong - zone {.importc: "tm_zone".}: cstring - type - StructTmPtr = ptr StructTm + proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime = + let utcDate = newDate(adjTime.seconds * 1000) + let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), + utcDate.getUTCDate(), utcDate.getUTCHours(), utcDate.getUTCMinutes(), + utcDate.getUTCSeconds(), 0) + + # This is as dumb as it looks - JS doesn't support years in the range + # 0-99 in the constructor because they are assumed to be 19xx... + # Because JS doesn't support timezone history, + # it doesn't really matter in practice. + if utcDate.getUTCFullYear() in 0 .. 99: + localDate.setFullYear(utcDate.getUTCFullYear()) - proc localtime(timer: ptr CTime): StructTmPtr {. importc: "localtime", header: "", tags: [].} + result.utcOffset = localDate.getTimezoneOffset() * secondsInMin + result.time = adjTime + initDuration(seconds = result.utcOffset) + result.isDst = false - proc toAdjUnix(tm: StructTm): int64 = - let epochDay = toEpochday(tm.monthday, (tm.month + 1).Month, tm.year.int + 1900) +else: + proc toAdjUnix(tm: Tm): int64 = + let epochDay = toEpochday(tm.tm_mday, (tm.tm_mon + 1).Month, + tm.tm_year.int + 1900) result = epochDay * secondsInDay - result.inc tm.hour * secondsInHour - result.inc tm.minute * 60 - result.inc tm.second + result.inc tm.tm_hour * secondsInHour + result.inc tm.tm_min * 60 + result.inc tm.tm_sec proc getLocalOffsetAndDst(unix: int64): tuple[offset: int, dst: bool] = # Windows can't handle unix < 0, so we fall back to unix = 0. @@ -960,7 +1017,7 @@ else: when defined(windows): if unix < 0: var a = 0.CTime - let tmPtr = localtime(addr(a)) + let tmPtr = localtime(a) if not tmPtr.isNil: let tm = tmPtr[] return ((0 - tm.toAdjUnix).int, false) @@ -969,10 +1026,10 @@ else: # In case of a 32-bit time_t, we fallback to the closest available # timezone information. var a = clamp(unix, low(CTime), high(CTime)).CTime - let tmPtr = localtime(addr(a)) + let tmPtr = localtime(a) if not tmPtr.isNil: let tm = tmPtr[] - return ((a.int64 - tm.toAdjUnix).int, tm.isdst > 0) + return ((a.int64 - tm.toAdjUnix).int, tm.tm_isdst > 0) return (0, false) proc localZonedTimeFromTime(time: Time): ZonedTime = @@ -981,7 +1038,7 @@ else: result.utcOffset = offset result.isDst = dst - proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime = + proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime = var adjUnix = adjTime.seconds let past = adjUnix - secondsInDay let (pastOffset, _) = getLocalOffsetAndDst(past) @@ -991,7 +1048,7 @@ else: var utcOffset: int if pastOffset == futureOffset: - utcOffset = pastOffset.int + utcOffset = pastOffset.int else: if pastOffset > futureOffset: adjUnix -= secondsInHour @@ -1025,8 +1082,8 @@ proc utc*(): TimeZone = proc local*(): TimeZone = ## Get the ``Timezone`` implementation for the local timezone. runnableExamples: - doAssert now().timezone == local() - doAssert local().name == "LOCAL" + doAssert now().timezone == local() + doAssert local().name == "LOCAL" if localInstance.isNil: localInstance = newTimezone("LOCAL", localZonedTimeFromTime, localZonedTimeFromAdjTime) @@ -1060,7 +1117,8 @@ proc getTime*(): Time {.tags: [TimeEffect], benign.} = elif defined(macosx) or defined(freebsd): var a: Timeval gettimeofday(a) - result = initTime(a.tv_sec.int64, convert(Microseconds, Nanoseconds, a.tv_usec.int)) + result = initTime(a.tv_sec.int64, + convert(Microseconds, Nanoseconds, a.tv_usec.int)) elif defined(posix): var ts: Timespec discard clock_gettime(realTimeClockId, ts) @@ -1081,13 +1139,17 @@ proc initTimeInterval*(nanoseconds, microseconds, milliseconds, days, weeks, months, years: int = 0): TimeInterval = ## Creates a new ``TimeInterval``. ## + ## This proc doesn't perform any normalization! For example, + ## ``initTimeInterval(hours = 24)`` and ``initTimeInterval(days = 1)`` are + ## not equal. + ## ## You can also use the convenience procedures called ``milliseconds``, ## ``seconds``, ``minutes``, ``hours``, ``days``, ``months``, and ``years``. - ## runnableExamples: - let day = initTimeInterval(hours=24) + let day = initTimeInterval(hours = 24) let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc()) doAssert $(dt + day) == "2000-01-02T12:00:00Z" + doAssert initTimeInterval(hours = 24) != initTimeInterval(days = 1) result.nanoseconds = nanoseconds result.microseconds = microseconds result.milliseconds = milliseconds @@ -1115,7 +1177,7 @@ proc `+`*(ti1, ti2: TimeInterval): TimeInterval = proc `-`*(ti: TimeInterval): TimeInterval = ## Reverses a time interval runnableExamples: - let day = -initTimeInterval(hours=24) + let day = -initTimeInterval(hours = 24) doAssert day.hours == -24 result = TimeInterval( @@ -1136,9 +1198,9 @@ proc `-`*(ti1, ti2: TimeInterval): TimeInterval = ## ## Time components are subtracted one-by-one, see output: runnableExamples: - let ti1 = initTimeInterval(hours=24) - let ti2 = initTimeInterval(hours=4) - doAssert (ti1 - ti2) == initTimeInterval(hours=20) + let ti1 = initTimeInterval(hours = 24) + let ti2 = initTimeInterval(hours = 4) + doAssert (ti1 - ti2) == initTimeInterval(hours = 20) result = ti1 + (-ti2) @@ -1155,13 +1217,13 @@ proc getClockStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} = ':' & intToStr(dt.second, 2) proc toParts* (ti: TimeInterval): TimeIntervalParts = - ## Converts a `TimeInterval` into an array consisting of its time units, - ## starting with nanoseconds and ending with years + ## Converts a ``TimeInterval`` into an array consisting of its time units, + ## starting with nanoseconds and ending with years. ## ## This procedure is useful for converting ``TimeInterval`` values to strings. ## E.g. then you need to implement custom interval printing runnableExamples: - var tp = toParts(initTimeInterval(years=1, nanoseconds=123)) + var tp = toParts(initTimeInterval(years = 1, nanoseconds = 123)) doAssert tp[Years] == 1 doAssert tp[Nanoseconds] == 123 @@ -1171,9 +1233,10 @@ proc toParts* (ti: TimeInterval): TimeIntervalParts = index += 1 proc `$`*(ti: TimeInterval): string = - ## Get string representation of `TimeInterval` + ## Get string representation of ``TimeInterval``. runnableExamples: - doAssert $initTimeInterval(years=1, nanoseconds=123) == "1 year and 123 nanoseconds" + doAssert $initTimeInterval(years = 1, nanoseconds = 123) == + "1 year and 123 nanoseconds" doAssert $initTimeInterval() == "0 nanoseconds" var parts: seq[string] = @[] @@ -1199,7 +1262,7 @@ proc milliseconds*(ms: int): TimeInterval {.inline.} = proc seconds*(s: int): TimeInterval {.inline.} = ## TimeInterval of ``s`` seconds. ## - ## ``echo getTime() + 5.second`` + ## ``echo getTime() + 5.seconds`` initTimeInterval(seconds = s) proc minutes*(m: int): TimeInterval {.inline.} = @@ -1238,7 +1301,8 @@ proc years*(y: int): TimeInterval {.inline.} = ## ``echo getTime() + 2.years`` initTimeInterval(years = y) -proc evaluateInterval(dt: DateTime, interval: TimeInterval): tuple[adjDur, absDur: Duration] = +proc evaluateInterval(dt: DateTime, interval: TimeInterval): + tuple[adjDur, absDur: Duration] = ## Evaluates how many nanoseconds the interval is worth ## in the context of ``dt``. ## The result in split into an adjusted diff and an absolute diff. @@ -1277,10 +1341,10 @@ proc evaluateInterval(dt: DateTime, interval: TimeInterval): tuple[adjDur, absDu minutes = interval.minutes, hours = interval.hours) - proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, hour: HourRange, minute: MinuteRange, second: SecondRange, - nanosecond: NanosecondRange, zone: Timezone = local()): DateTime = + nanosecond: NanosecondRange, + zone: Timezone = local()): DateTime = ## Create a new ``DateTime`` in the specified timezone. runnableExamples: let dt1 = initDateTime(30, mMar, 2017, 00, 00, 00, 00, utc()) @@ -1288,12 +1352,12 @@ proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, assertValidDate monthday, month, year let dt = DateTime( - monthday: monthday, - year: year, - month: month, - hour: hour, - minute: minute, - second: second, + monthday: monthday, + year: year, + month: month, + hour: hour, + minute: minute, + second: second, nanosecond: nanosecond ) result = initDateTime(zone.zonedTimeFromAdjTime(dt.toAdjTime), zone) @@ -1310,14 +1374,15 @@ proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, proc `+`*(dt: DateTime, interval: TimeInterval): DateTime = ## Adds ``interval`` to ``dt``. Components from ``interval`` are added - ## in the order of their size, i.e first the ``years`` component, then the ``months`` - ## component and so on. The returned ``DateTime`` will have the same timezone as the input. - ## - ## Note that when adding months, monthday overflow is allowed. This means that if the resulting - ## month doesn't have enough days it, the month will be incremented and the monthday will be - ## set to the number of days overflowed. So adding one month to `31 October` will result in `31 November`, - ## which will overflow and result in `1 December`. + ## in the order of their size, i.e first the ``years`` component, then the + ## ``months`` component and so on. The returned ``DateTime`` will have the + ## same timezone as the input. ## + ## Note that when adding months, monthday overflow is allowed. This means that + ## if the resulting month doesn't have enough days it, the month will be + ## incremented and the monthday will be set to the number of days overflowed. + ## So adding one month to `31 October` will result in `31 November`, which + ## will overflow and result in `1 December`. runnableExamples: let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) doAssert $(dt + 1.months) == "2017-04-30T00:00:00Z" @@ -1337,9 +1402,10 @@ proc `+`*(dt: DateTime, interval: TimeInterval): DateTime = result = initDateTime(zt, dt.timezone) proc `-`*(dt: DateTime, interval: TimeInterval): DateTime = - ## Subtract ``interval`` from ``dt``. Components from ``interval`` are subtracted - ## in the order of their size, i.e first the ``years`` component, then the ``months`` - ## component and so on. The returned ``DateTime`` will have the same timezone as the input. + ## Subtract ``interval`` from ``dt``. Components from ``interval`` are + ## subtracted in the order of their size, i.e first the ``years`` component, + ## then the ``months`` component and so on. The returned ``DateTime`` will + ## have the same timezone as the input. runnableExamples: let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) doAssert $(dt - 5.days) == "2017-03-25T00:00:00Z" @@ -1373,15 +1439,15 @@ proc `-`*(dt1, dt2: DateTime): Duration = dt1.toTime - dt2.toTime proc `<`*(a, b: DateTime): bool = - ## Returns true iff ``a < b``, that is iff a happened before b. + ## Returns true iff ``a`` happened before ``b``. return a.toTime < b.toTime -proc `<=` * (a, b: DateTime): bool = - ## Returns true iff ``a <= b``. +proc `<=`*(a, b: DateTime): bool = + ## Returns true iff ``a`` happened before or at the same time as ``b``. return a.toTime <= b.toTime proc `==`*(a, b: DateTime): bool = - ## Returns true if ``a == b``, that is if both dates represent the same point in time. + ## Returns true iff ``a`` and ``b`` represent the same point in time. return a.toTime == b.toTime proc isStaticInterval(interval: TimeInterval): bool = @@ -1482,8 +1548,8 @@ proc between*(startDt, endDt: DateTime): TimeInterval = #Months if endDt.month < startDt.month: - result.months = endDt.month.int() + 12 - startDt.month.int() - endDt.year -= 1 + result.months = endDt.month.int() + 12 - startDt.month.int() + endDt.year -= 1 else: result.months = endDt.month.int() - startDt.month.int() @@ -1604,7 +1670,7 @@ type TimeFormatParseError* = object of ValueError ## \ ## Raised when parsing a ``TimeFormat`` string fails. -const FormatLiterals = { ' ', '-', '/', ':', '(', ')', '[', ']', ',' } +const FormatLiterals = {' ', '-', '/', ':', '(', ')', '[', ']', ','} proc `$`*(f: TimeFormat): string = ## Returns the format string that was used to construct ``f``. @@ -1672,9 +1738,9 @@ iterator tokens(f: string): tuple[kind: FormatTokenKind, token: string] = i.inc yield (tkLiteral, token) of FormatLiterals: - yieldCurrToken() - yield (tkLiteral, $f[i]) - i.inc + yieldCurrToken() + yield (tkLiteral, $f[i]) + i.inc else: # Check if the letter being added matches previous accumulated buffer. if currToken.len == 0 or currToken[0] == f[i]: @@ -1832,12 +1898,12 @@ proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string) = else: result.add '+' & $year of UUUU: - result.add $dt.year + result.add $dt.year of z, zz, zzz, zzzz: if dt.timezone != nil and dt.timezone.name == "Etc/UTC": result.add 'Z' else: - result.add if -dt.utcOffset >= 0: '+' else: '-' + result.add if -dt.utcOffset >= 0: '+' else: '-' let absOffset = abs(dt.utcOffset) case pattern: of z: @@ -1856,7 +1922,7 @@ proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string) = result.add h & ":" & m & ":" & s else: assert false of g: - result.add if dt.year < 1: "BC" else: "AD" + result.add if dt.year < 1: "BC" else: "AD" of Lit: assert false # Can't happen proc parsePattern(input: string, pattern: FormatPattern, i: var int, @@ -2017,7 +2083,7 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int, result = year > 0 of yyyy: let year = - if input[i] in { '+', '-' }: + if input[i] in {'+', '-'}: takeInt(4..high(int), allowSign = true) else: takeInt(4..4) @@ -2029,7 +2095,7 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int, result = year > 0 of uuuu: let year = - if input[i] in { '+', '-' }: + if input[i] in {'+', '-'}: takeInt(4..high(int), allowSign = true) else: takeInt(4..4) @@ -2244,7 +2310,7 @@ proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime if patIdx <= f.patterns.high: raiseParseException(f, input, - "Parsing ended but there was still patterns remaining") + "Parsing ended but there was still patterns remaining") result = toDateTime(parsed, zone, f, input) @@ -2261,8 +2327,8 @@ proc parse*(input, f: string, tz: Timezone = local()): DateTime let dtFormat = initTimeFormat(f) result = input.parse(dtFormat, tz) -proc parse*(input: string, f: static[string], zone: Timezone = local()): DateTime - {.raises: [TimeParseError, Defect].} = +proc parse*(input: string, f: static[string], zone: Timezone = local()): + DateTime {.raises: [TimeParseError, Defect].} = ## Overload that validates ``f`` at compile time. const f2 = initTimeFormat(f) result = input.parse(f2, zone) @@ -2298,7 +2364,7 @@ proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} = result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz") proc `$`*(time: Time): string {.tags: [], raises: [], benign.} = - ## converts a `Time` value to a string representation. It will use the local + ## Converts a `Time` value to a string representation. It will use the local ## time zone and use the format ``yyyy-MM-dd'T'HH-mm-sszzz``. runnableExamples: let dt = initDateTime(01, mJan, 1970, 00, 00, 00, local()) @@ -2347,7 +2413,8 @@ when not defined(JS): type Clock {.importc: "clock_t".} = distinct int - proc getClock(): Clock {.importc: "clock", header: "", tags: [TimeEffect].} + proc getClock(): Clock + {.importc: "clock", header: "", tags: [TimeEffect].} var clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl.}: int @@ -2405,59 +2472,68 @@ when defined(JS): # Deprecated procs when not defined(JS): - proc unixTimeToWinTime*(time: CTime): int64 {.deprecated: "Use toWinTime instead".} = + proc unixTimeToWinTime*(time: CTime): int64 + {.deprecated: "Use toWinTime instead".} = ## Converts a UNIX `Time` (``time_t``) to a Windows file time ## ## **Deprecated:** use ``toWinTime`` instead. result = int64(time) * rateDiff + epochDiff - proc winTimeToUnixTime*(time: int64): CTime {.deprecated: "Use fromWinTime instead".} = + proc winTimeToUnixTime*(time: int64): CTime + {.deprecated: "Use fromWinTime instead".} = ## Converts a Windows time to a UNIX `Time` (``time_t``) ## ## **Deprecated:** use ``fromWinTime`` instead. result = CTime((time - epochDiff) div rateDiff) -proc initInterval*(seconds, minutes, hours, days, months, - years: int = 0): TimeInterval {.deprecated.} = +proc initInterval*(seconds, minutes, hours, days, months, years: int = 0): + TimeInterval {.deprecated.} = ## **Deprecated since v0.18.0:** use ``initTimeInterval`` instead. initTimeInterval(0, 0, 0, seconds, minutes, hours, days, 0, months, years) -proc fromSeconds*(since1970: float): Time {.tags: [], raises: [], benign, deprecated.} = +proc fromSeconds*(since1970: float): Time + {.tags: [], raises: [], benign, deprecated.} = ## Takes a float which contains the number of seconds since the unix epoch and ## returns a time object. ## ## **Deprecated since v0.18.0:** use ``fromUnix`` instead - let nanos = ((since1970 - since1970.int64.float) * convert(Seconds, Nanoseconds, 1).float).int + let nanos = ((since1970 - since1970.int64.float) * + convert(Seconds, Nanoseconds, 1).float).int initTime(since1970.int64, nanos) -proc fromSeconds*(since1970: int64): Time {.tags: [], raises: [], benign, deprecated.} = +proc fromSeconds*(since1970: int64): Time + {.tags: [], raises: [], benign, deprecated.} = ## Takes an int which contains the number of seconds since the unix epoch and ## returns a time object. ## ## **Deprecated since v0.18.0:** use ``fromUnix`` instead fromUnix(since1970) -proc toSeconds*(time: Time): float {.tags: [], raises: [], benign, deprecated.} = +proc toSeconds*(time: Time): float + {.tags: [], raises: [], benign, deprecated.} = ## Returns the time in seconds since the unix epoch. ## ## **Deprecated since v0.18.0:** use ``toUnix`` instead time.seconds.float + time.nanosecond / convert(Seconds, Nanoseconds, 1) -proc getLocalTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} = +proc getLocalTime*(time: Time): DateTime + {.tags: [], raises: [], benign, deprecated.} = ## Converts the calendar time `time` to broken-time representation, ## expressed relative to the user's specified time zone. ## ## **Deprecated since v0.18.0:** use ``local`` instead time.local -proc getGMTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} = +proc getGMTime*(time: Time): DateTime + {.tags: [], raises: [], benign, deprecated.} = ## Converts the calendar time `time` to broken-down time representation, ## expressed in Coordinated Universal Time (UTC). ## ## **Deprecated since v0.18.0:** use ``utc`` instead time.utc -proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign, deprecated.} = +proc getTimezone*(): int + {.tags: [TimeEffect], raises: [], benign, deprecated.} = ## Returns the offset of the local (non-DST) timezone in seconds west of UTC. ## ## **Deprecated since v0.18.0:** use ``now().utcOffset`` to get the current @@ -2465,45 +2541,14 @@ proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign, deprecated.} when defined(JS): return newDate().getTimezoneOffset() * 60 elif defined(freebsd) or defined(netbsd) or defined(openbsd): - var a: CTime - discard time(a) - let lt = localtime(addr(a)) - # BSD stores in `gmtoff` offset east of UTC in seconds, - # but posix systems using west of UTC in seconds - return -(lt.gmtoff) + # This is wrong since it will include DST offsets, but the behavior has + # always been wrong for bsd and the proc is deprecated so lets ignore it. + return now().utcOffset else: return timezone -proc timeInfoToTime*(dt: DateTime): Time {.tags: [], benign, deprecated.} = - ## Converts a broken-down time structure to calendar time representation. - ## - ## **Deprecated since v0.14.0:** use ``toTime`` instead. - dt.toTime - -when defined(JS): - var start = getTime() - proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} = - let dur = getTime() - start - result = (convert(Seconds, Milliseconds, dur.seconds) + - convert(Nanoseconds, Milliseconds, dur.nanosecond)).int -else: - proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} = - ## get the milliseconds from the start of the program. - ## - ## **Deprecated since v0.8.10:** use ``epochTime`` or ``cpuTime`` instead. - when defined(macosx): - result = toInt(toFloat(int(getClock())) / (toFloat(clocksPerSec) / 1000.0)) - else: - result = int(getClock()) div (clocksPerSec div 1000) - -proc timeToTimeInterval*(t: Time): TimeInterval {.deprecated.} = - ## Converts a Time to a TimeInterval. - ## - ## **Deprecated since v0.14.0:** use ``toTimeInterval`` instead. - # Milliseconds not available from Time - t.toTimeInterval() - -proc getDayOfWeek*(day, month, year: int): WeekDay {.tags: [], raises: [], benign, deprecated.} = +proc getDayOfWeek*(day, month, year: int): WeekDay + {.tags: [], raises: [], benign, deprecated.} = ## **Deprecated since v0.18.0:** use ## ``getDayOfWeek(monthday: MonthdayRange; month: Month; year: int)`` instead. getDayOfWeek(day, month.Month, year) -- cgit 1.4.1-2-gfad0 From d998cb58dd9a29cb7b739ee4daa6a0528810790d Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Thu, 10 Jan 2019 17:19:35 +0530 Subject: void object fields are now ignored by codegen and fields/fieldPairs iterator (#10144) * Codegen now ignores object fields of type void * Fix `$` bug for objects/tuples where it does not add a comma * fields/fieldPairs iterators now ignore void types * Use `isEmptyType` instead of checking for `tyVoid` directly --- compiler/ccgtypes.nim | 2 ++ compiler/jsgen.nim | 2 ++ compiler/semfields.nim | 3 +++ lib/system.nim | 1 + tests/objects/t3734.nim | 17 +++++++++++++++++ 5 files changed, 25 insertions(+) create mode 100644 tests/objects/t3734.nim diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 243aa87de..23e16cb93 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -1041,6 +1041,8 @@ proc genObjectFields(m: BModule, typ, origType: PType, n: PNode, expr: Rope; else: internalError(m.config, n.info, "genObjectFields(nkRecCase)") of nkSym: var field = n.sym + # Do not produce code for void types + if isEmptyType(field.typ): return if field.bitsize == 0: if field.loc.r == nil: fillObjectFields(m, typ) if field.loc.t == nil: diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 83d205bc2..2f5e202e0 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -1502,6 +1502,8 @@ proc createRecordVarAux(p: PProc, rec: PNode, excludedFieldIDs: IntSet, output: for i in countup(1, sonsLen(rec) - 1): createRecordVarAux(p, lastSon(rec.sons[i]), excludedFieldIDs, output) of nkSym: + # Do not produce code for void types + if isEmptyType(rec.sym.typ): return if rec.sym.id notin excludedFieldIDs: if output.len > 0: output.add(", ") output.addf("$#: ", [mangleName(p.module, rec.sym)]) diff --git a/compiler/semfields.nim b/compiler/semfields.nim index 07321f477..d65d962cb 100644 --- a/compiler/semfields.nim +++ b/compiler/semfields.nim @@ -19,6 +19,9 @@ type c: PContext proc instFieldLoopBody(c: TFieldInstCtx, n: PNode, forLoop: PNode): PNode = + if c.field != nil and isEmptyType(c.field.typ): + result = newNode(nkEmpty) + return case n.kind of nkEmpty..pred(nkIdent), succ(nkSym)..nkNilLit: result = n of nkIdent, nkSym: diff --git a/lib/system.nim b/lib/system.nim index dff195402..b8ff85f49 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2700,6 +2700,7 @@ proc `$`*[T: tuple|object](x: T): string = firstElement = false else: result.add("...") + firstElement = false when not isNamed: if count == 1: result.add(",") # $(1,) should print as the semantically legal (1,) diff --git a/tests/objects/t3734.nim b/tests/objects/t3734.nim new file mode 100644 index 000000000..cebef6081 --- /dev/null +++ b/tests/objects/t3734.nim @@ -0,0 +1,17 @@ +discard """ +output: "i0" +""" + +type + Application = object + config: void + i: int + f: void + +proc printFields(rec: Application) = + for k, v in fieldPairs(rec): + echo k, v + +var app: Application + +printFields(app) -- cgit 1.4.1-2-gfad0 From 8e600b78ca965117a053751f38ccaffa3e16780a Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 10 Jan 2019 13:44:44 +0100 Subject: add unsetControlCHook to remove a Ctrl-C hook after it was set (#7267) * add unsetControlCHook to remove a Ctrl-C hook after it was set Adds the inverse proc to setControlCHook in order to lift a Ctrl-C hook after it has been set. * remove check for noSignalHandler in system/excpt.nim --- lib/system.nim | 4 ++++ lib/system/embedded.nim | 1 + lib/system/excpt.nim | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/lib/system.nim b/lib/system.nim index b8ff85f49..fb52ee9eb 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -3458,6 +3458,10 @@ when not defined(JS): #and not defined(nimscript): ## allows you to override the behaviour of your application when CTRL+C ## is pressed. Only one such hook is supported. + when not defined(useNimRtl): + proc unsetControlCHook*() + ## reverts a call to setControlCHook + proc writeStackTrace*() {.tags: [], gcsafe.} ## writes the current stack trace to ``stderr``. This is only works ## for debug builds. Since it's usually used for debugging, this diff --git a/lib/system/embedded.nim b/lib/system/embedded.nim index 4d453fcca..fb89e7f0f 100644 --- a/lib/system/embedded.nim +++ b/lib/system/embedded.nim @@ -40,6 +40,7 @@ proc reraiseException() {.compilerRtl.} = proc writeStackTrace() = discard +proc unsetControlCHook() = discard proc setControlCHook(hook: proc () {.noconv.}) = discard proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} = diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index 84a1da343..3efefead4 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -536,3 +536,8 @@ proc setControlCHook(hook: proc () {.noconv.}) = # ugly cast, but should work on all architectures: type SignalHandler = proc (sign: cint) {.noconv, benign.} c_signal(SIGINT, cast[SignalHandler](hook)) + +when not defined(useNimRtl): + proc unsetControlCHook() = + # proc to unset a hook set by setControlCHook + c_signal(SIGINT, signalHandler) -- cgit 1.4.1-2-gfad0 From 0bb76dde547ff9f3e80f7421d4f2c556d7aefc4d Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Thu, 10 Jan 2019 08:49:57 -0800 Subject: fixes #10030 bootstrap is insulated from user config (#10244) --- build_all.sh | 6 +++++- koch.nim | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/build_all.sh b/build_all.sh index 6c28e7407..6828bbbd8 100644 --- a/build_all.sh +++ b/build_all.sh @@ -26,6 +26,10 @@ build_nim_csources(){ [ -f $nim_csources ] || echo_run build_nim_csources -echo_run bin/nim c koch # Note: if fails, may need to `cd csources && git pull` +# Note: if fails, may need to `cd csources && git pull` +# Note: --skipUserCfg is to prevent newer flags from +# breaking bootstrap phase +echo_run bin/nim c --skipUserCfg koch + echo_run ./koch boot -d:release echo_run ./koch tools # Compile Nimble and other tools. diff --git a/koch.nim b/koch.nim index 845dffada..a77ad3144 100644 --- a/koch.nim +++ b/koch.nim @@ -285,8 +285,13 @@ proc boot(args: string) = copyExe(findStartNim(), 0.thVersion) for i in 0..2: echo "iteration: ", i+1 - exec i.thVersion & " $# $# --nimcache:$# compiler" / "nim.nim" % [bootOptions, args, - smartNimcache] + let extraOption = if i == 0: + "--skipUserCfg" + # forward compatibility: for bootstrap (1st iteration), avoid user flags + # that could break things, see #10030 + else: "" + exec i.thVersion & " $# $# $# --nimcache:$# compiler" / "nim.nim" % + [bootOptions, extraOption, args, smartNimcache] if sameFileContent(output, i.thVersion): copyExe(output, finalDest) echo "executables are equal: SUCCESS!" -- cgit 1.4.1-2-gfad0 From a5fe676f37cfabfec0b4ea1a2008fe82058a7059 Mon Sep 17 00:00:00 2001 From: Ico Doornekamp Date: Thu, 10 Jan 2019 17:57:21 +0100 Subject: Added 'Limitations' section to nimscript manual listing the restrictions of the VM (#10209) --- doc/nims.rst | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/doc/nims.rst b/doc/nims.rst index 034ad1fda..6cc894b91 100644 --- a/doc/nims.rst +++ b/doc/nims.rst @@ -26,9 +26,28 @@ previous settings): ``$project.nim``. This file can be skipped with the same ``--skipProjCfg`` command line option. -The VM cannot deal with ``importc`` because the FFI is not -available. So the stdlib modules using ``importc`` cannot be used with -Nim's VM. However, at least the following modules are available: +Limitations +================================= + +NimScript is subject to some limitations caused by the implementation of the VM +(virtual machine): + +* Nim's FFI (foreign function interface) is not available in NimScript. This + means that any stdlib module which relies on ``importc`` can not be used in + the VM. + +* ``ptr`` operations are are hard to emulate with the symbolic representation + the VM uses. They are available and tested extensively but there are bugs left. + +* ``var T`` function arguments rely on ``ptr`` operations internally and might + also be problematic in some cases. + +* More than one level of `ref` is generally not supported (for example, the type + `ref ref int`). + +* multimethods are not available. + +Given the above restrictions, at least the following modules are available: * `macros `_ * `os `_ -- cgit 1.4.1-2-gfad0 From ab425d793af1a5c0b221bfbeab382b4781479c63 Mon Sep 17 00:00:00 2001 From: Jörg Wollenschläger Date: Fri, 11 Jan 2019 03:18:00 +0900 Subject: [RFC] Better atomics (#8620) * Initial version of C++11 style atomics * Make Atomic[T] always concrete --- lib/pure/concurrency/atomics.nim | 378 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 lib/pure/concurrency/atomics.nim diff --git a/lib/pure/concurrency/atomics.nim b/lib/pure/concurrency/atomics.nim new file mode 100644 index 000000000..5a3709f87 --- /dev/null +++ b/lib/pure/concurrency/atomics.nim @@ -0,0 +1,378 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2018 Jörg Wollenschläger +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Types and operations for atomic operations and lockless algorithms. + +import macros + +when defined(cpp) or defined(nimdoc): + # For the C++ backend, types and operations map directly to C++11 atomics. + + {.push, header: "".} + + type + MemoryOrder* {.importcpp: "std::memory_order".} = enum + ## Specifies how non-atomic operations can be reordered around atomic + ## operations. + + moRelaxed + ## No ordering constraints. Only the atomicity and ordering against + ## other atomic operations is guaranteed. + + moConsume + ## This ordering is currently discouraged as it's semantics are + ## being revised. Acquire operations should be preferred. + + moAcquire + ## When applied to a load operation, no reads or writes in the + ## current thread can be reordered before this operation. + + moRelease + ## When applied to a store operation, no reads or writes in the + ## current thread can be reorderd after this operation. + + moAcquireRelease + ## When applied to a read-modify-write operation, this behaves like + ## both an acquire and a release operation. + + moSequentiallyConsistent + ## Behaves like Acquire when applied to load, like Release when + ## applied to a store and like AcquireRelease when applied to a + ## read-modify-write operation. + ## Also garantees that all threads observe the same total ordering + ## with other moSequentiallyConsistent operations. + + type + Atomic* {.importcpp: "std::atomic".} [T] = object + ## An atomic object with underlying type `T`. + + AtomicFlag* {.importcpp: "std::atomic_flag".} = object + ## An atomic boolean state. + + # Access operations + + proc load*[T](location: var Atomic[T]; order: MemoryOrder = moSequentiallyConsistent): T {.importcpp: "#.load(@)".} + ## Atomically obtains the value of the atomic object. + + proc store*[T](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.importcpp: "#.store(@)".} + ## Atomically replaces the value of the atomic object with the `desired` + ## value. + + proc exchange*[T](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.importcpp: "#.exchange(@)".} + ## Atomically replaces the value of the atomic object with the `desired` + ## value and returns the old value. + + proc compareExchange*[T](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.importcpp: "#.compare_exchange_strong(@)".} + ## Atomically compares the value of the atomic object with the `expected` + ## value and performs exchange with the `desired` one if equal or load if + ## not. Returns true if the exchange was successful. + + proc compareExchange*[T](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.importcpp: "#.compare_exchange_strong(@)".} + ## Same as above, but allows for different memory orders for success and + ## failure. + + proc compareExchangeWeak*[T](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.importcpp: "#.compare_exchange_weak(@)".} + ## Same as above, but is allowed to fail spuriously. + + proc compareExchangeWeak*[T](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.importcpp: "#.compare_exchange_weak(@)".} + ## Same as above, but allows for different memory orders for success and + ## failure. + + # Numerical operations + + proc fetchAdd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importcpp: "#.fetch_add(@)".} + ## Atomically adds a `value` to the atomic integer and returns the + ## original value. + + proc fetchSub*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importcpp: "#.fetch_sub(@)".} + ## Atomically subtracts a `value` to the atomic integer and returns the + ## original value. + + proc fetchAnd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importcpp: "#.fetch_and(@)".} + ## Atomically replaces the atomic integer with it's bitwise AND + ## with the specified `value` and returns the original value. + + proc fetchOr*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importcpp: "#.fetch_or(@)".} + ## Atomically replaces the atomic integer with it's bitwise OR + ## with the specified `value` and returns the original value. + + proc fetchXor*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importcpp: "#.fetch_xor(@)".} + ## Atomically replaces the atomic integer with it's bitwise XOR + ## with the specified `value` and returns the original value. + + # Flag operations + + proc testAndSet*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent): bool {.importcpp: "#.test_and_set(@)".} + ## Atomically sets the atomic flag to true and returns the original value. + + proc clear*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent) {.importcpp: "#.clear(@)".} + ## Atomically sets the value of the atomic flag to false. + + proc fence*(order: MemoryOrder) {.importcpp: "std::atomic_thread_fence(@)".} + ## Ensures memory ordering without using atomic operations. + + proc signalFence*(order: MemoryOrder) {.importcpp: "std::atomic_signal_fence(@)".} + ## Prevents reordering of accesses by the compiler as would fence, but + ## inserts no CPU instructions for memory ordering. + + {.pop.} + +else: + # For the C backend, atomics map to C11 built-ins on GCC and Clang for + # trivial Nim types. Other types are implemented using spin locks. + # This could be overcome by supporting advanced importc-patterns. + + # Since MSVC does not implement C11, we fall back to MS intrinsics + # where available. + + type + Trivial = SomeNumber | bool | ptr | pointer + # A type that is known to be atomic and whose size is known at + # compile time to be 8 bytes or less + + template nonAtomicType(T: typedesc[Trivial]): typedesc = + # Maps types to integers of the same size + when sizeof(T) == 1: int8 + elif sizeof(T) == 2: int16 + elif sizeof(T) == 4: int32 + elif sizeof(T) == 8: int64 + + when defined(vcc): + + # TODO: Trivial types should be volatile and use VC's special volatile + # semantics for store and loads. + + type + MemoryOrder* = enum + moRelaxed + moConsume + moAcquire + moRelease + moAcquireRelease + moSequentiallyConsistent + + Atomic*[T] = object + when T is Trivial: + value: T.nonAtomicType + else: + nonAtomicValue: T + guard: AtomicFlag + + AtomicFlag* = distinct int8 + + {.push header: "".} + + # MSVC intrinsics + proc interlockedExchange(location: pointer; desired: int8): int8 {.importc: "_InterlockedExchange8".} + proc interlockedExchange(location: pointer; desired: int16): int16 {.importc: "_InterlockedExchange".} + proc interlockedExchange(location: pointer; desired: int32): int32 {.importc: "_InterlockedExchange16".} + proc interlockedExchange(location: pointer; desired: int64): int64 {.importc: "_InterlockedExchange64".} + + proc interlockedCompareExchange(location: pointer; desired, expected: int8): int8 {.importc: "_InterlockedCompareExchange8".} + proc interlockedCompareExchange(location: pointer; desired, expected: int16): int16 {.importc: "_InterlockedCompareExchange16".} + proc interlockedCompareExchange(location: pointer; desired, expected: int32): int32 {.importc: "_InterlockedCompareExchange".} + proc interlockedCompareExchange(location: pointer; desired, expected: int64): int64 {.importc: "_InterlockedCompareExchange64".} + + proc interlockedAnd(location: pointer; value: int8): int8 {.importc: "_InterlockedAnd8".} + proc interlockedAnd(location: pointer; value: int16): int16 {.importc: "_InterlockedAnd16".} + proc interlockedAnd(location: pointer; value: int32): int32 {.importc: "_InterlockedAnd".} + proc interlockedAnd(location: pointer; value: int64): int64 {.importc: "_InterlockedAnd64".} + + proc interlockedOr(location: pointer; value: int8): int8 {.importc: "_InterlockedOr8".} + proc interlockedOr(location: pointer; value: int16): int16 {.importc: "_InterlockedOr16".} + proc interlockedOr(location: pointer; value: int32): int32 {.importc: "_InterlockedOr".} + proc interlockedOr(location: pointer; value: int64): int64 {.importc: "_InterlockedOr64".} + + proc interlockedXor(location: pointer; value: int8): int8 {.importc: "_InterlockedXor8".} + proc interlockedXor(location: pointer; value: int16): int16 {.importc: "_InterlockedXor16".} + proc interlockedXor(location: pointer; value: int32): int32 {.importc: "_InterlockedXor".} + proc interlockedXor(location: pointer; value: int64): int64 {.importc: "_InterlockedXor64".} + + proc fence(order: MemoryOrder): int64 {.importc: "_ReadWriteBarrier()".} + proc signalFence(order: MemoryOrder): int64 {.importc: "_ReadWriteBarrier()".} + + {.pop.} + + proc testAndSet*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent): bool = + interlockedOr(addr(location), 1'i8) == 1'i8 + proc clear*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent) = + discard interlockedAnd(addr(location), 0'i8) + + proc load*[T: Trivial](location: var T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedOr(addr(location), (nonAtomicType(T))0)) + proc store*[T: Trivial](location: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.inline.} = + discard interlockedExchange(addr(location), cast[nonAtomicType(T)](desired)) + + proc exchange*[T: Trivial](location: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedExchange(addr(location), cast[int64](desired))) + proc compareExchange*[T: Trivial](location: var T; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = + cast[T](interlockedCompareExchange(addr(location), cast[nonAtomicType(T)](desired), cast[nonAtomicType(T)](expected))) == expected + proc compareExchange*[T: Trivial](location: var T; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = + compareExchange(location, expected, desired, order, order) + proc compareExchangeWeak*[T: Trivial](location: var T; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = + compareExchange(location, expected, desired, success, failure) + proc compareExchangeWeak*[T: Trivial](location: var T; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = + compareExchangeWeak(location, expected, desired, order, order) + + proc fetchAdd*[T: SomeInteger](location: var T; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + let currentValue = location.load() + while not compareExchangeWeak(location, currentValue, currentValue + value): discard + proc fetchSub*[T: SomeInteger](location: var T; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + fetchAdd(location, -value, order) + proc fetchAnd*[T: SomeInteger](location: var T; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedAnd(addr(location), cast[nonAtomicType(T)](value))) + proc fetchOr*[T: SomeInteger](location: var T; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedOr(addr(location), cast[nonAtomicType(T)](value))) + proc fetchXor*[T: SomeInteger](location: var T; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedXor(addr(location), cast[nonAtomicType(T)](value))) + + else: + {.push, header: "".} + + type + MemoryOrder* {.importc: "memory_order".} = enum + moRelaxed + moConsume + moAcquire + moRelease + moAcquireRelease + moSequentiallyConsistent + + type + # Atomic* {.importcpp: "_Atomic('0)".} [T] = object + + AtomicInt8 {.importc: "_Atomic NI8".} = object + AtomicInt16 {.importc: "_Atomic NI16".} = object + AtomicInt32 {.importc: "_Atomic NI32".} = object + AtomicInt64 {.importc: "_Atomic NI64".} = object + + template atomicType(T: typedesc[Trivial]): untyped = + # Maps the size of a trivial type to it's internal atomic type + when sizeof(T) == 1: AtomicInt8 + elif sizeof(T) == 2: AtomicInt16 + elif sizeof(T) == 4: AtomicInt32 + elif sizeof(T) == 8: AtomicInt64 + + type + AtomicFlag* {.importc: "atomic_flag".} = object + + Atomic*[T] = object + when T is Trivial: + value: T.atomicType + else: + nonAtomicValue: T + guard: AtomicFlag + + #proc init*[T](location: var Atomic[T]; value: T): T {.importcpp: "atomic_init(@)".} + proc atomic_load_explicit[T, A](location: ptr A; order: MemoryOrder): T {.importc.} + proc atomic_store_explicit[T, A](location: ptr A; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.importc.} + proc atomic_exchange_explicit[T, A](location: ptr A; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} + proc atomic_compare_exchange_strong_explicit[T, A](location: ptr A; expected: ptr T; desired: T; success, failure: MemoryOrder): bool {.importc.} + proc atomic_compare_exchange_weak_explicit[T, A](location: ptr A; expected: ptr T; desired: T; success, failure: MemoryOrder): bool {.importc.} + + # Numerical operations + proc atomic_fetch_add_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} + proc atomic_fetch_sub_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} + proc atomic_fetch_and_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} + proc atomic_fetch_or_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} + proc atomic_fetch_xor_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} + + # Flag operations + # var ATOMIC_FLAG_INIT {.importc, nodecl.}: AtomicFlag + # proc init*(location: var AtomicFlag) {.inline.} = location = ATOMIC_FLAG_INIT + proc testAndSet*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent): bool {.importc: "atomic_flag_test_and_set_explicit".} + proc clear*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent) {.importc: "atomic_flag_clear_explicit".} + + proc fence*(order: MemoryOrder) {.importc: "atomic_thread_fence".} + proc signalFence*(order: MemoryOrder) {.importc: "atomic_signal_fence".} + + {.pop.} + + proc load*[T: Trivial](location: var Atomic[T]; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](atomic_load_explicit[nonAtomicType(T), type(location.value)](addr(location.value), order)) + proc store*[T: Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.inline.} = + atomic_store_explicit(addr(location.value), cast[nonAtomicType(T)](desired), order) + proc exchange*[T: Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](atomic_exchange_explicit(addr(location.value), cast[nonAtomicType(T)](desired), order)) + proc compareExchange*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = + atomic_compare_exchange_strong_explicit(addr(location.value), cast[ptr nonAtomicType(T)](addr(expected)), cast[nonAtomicType(T)](desired), success, failure) + proc compareExchange*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = + compareExchange(location, expected, desired, order, order) + + proc compareExchangeWeak*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = + atomic_compare_exchange_weak_explicit(addr(location.value), cast[ptr nonAtomicType(T)](addr(expected)), cast[nonAtomicType(T)](desired), success, failure) + proc compareExchangeWeak*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = + compareExchangeWeak(location, expected, desired, order, order) + + # Numerical operations + proc fetchAdd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](atomic_fetch_add_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) + proc fetchSub*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](atomic_fetch_sub_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) + proc fetchAnd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](atomic_fetch_and_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) + proc fetchOr*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](atomic_fetch_or_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) + proc fetchXor*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](atomic_fetch_xor_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) + + template withLock[T: not Trivial](location: var Atomic[T]; order: MemoryOrder; body: untyped): untyped = + while location.guard.testAndSet(moAcquire): discard + body + location.guard.clear(moRelease) + + proc load*[T: not Trivial](location: var Atomic[T]; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + withLock(location, order): + result = location.nonAtomicValue + + proc store*[T: not Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.inline.} = + withLock(location, order): + location.nonAtomicValue = desired + + proc exchange*[T: not Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + withLock(location, order): + result = location.nonAtomicValue + location.nonAtomicValue = desired + + proc compareExchange*[T: not Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = + withLock(location, success): + if location.nonAtomicValue != expected: + return false + swap(location.nonAtomicValue, expected) + return true + + proc compareExchangeWeak*[T: not Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = + withLock(location, success): + if location.nonAtomicValue != expected: + return false + swap(location.nonAtomicValue, expected) + return true + + proc compareExchange*[T: not Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = + compareExchange(location, expected, desired, order, order) + + proc compareExchangeWeak*[T: not Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = + compareExchangeWeak(location, expected, desired, order, order) + +proc atomicInc*[T: SomeInteger](location: var Atomic[T]; value: T = 1) {.inline.} = + ## Atomically increments the atomic integer by some `value`. + discard location.fetchAdd(value) + +proc atomicDec*[T: SomeInteger](location: var Atomic[T]; value: T = 1) {.inline.} = + ## Atomically decrements the atomic integer by some `value`. + discard location.fetchSub(value) + +proc `+=`*[T: SomeInteger](location: var Atomic[T]; value: T) {.inline.} = + ## Atomically increments the atomic integer by some `value`. + discard location.fetchAdd(value) + +proc `-=`*[T: SomeInteger](location: var Atomic[T]; value: T) {.inline.} = + ## Atomically decrements the atomic integer by some `value`. + discard location.fetchSub(value) + \ No newline at end of file -- cgit 1.4.1-2-gfad0 From 540d102d87645394d1f6e1c5116f9507a269882c Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Thu, 10 Jan 2019 22:59:20 -0800 Subject: fix #10261 unittest now returns 1 on error instead of buggy behavior (#10264) --- 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 135a24e9a..ce147cccc 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -499,7 +499,7 @@ template test*(name, body) {.dirty.} = finally: if testStatusIMPL == FAILED: - programResult += 1 + programResult = 1 let testResult = TestResult( suiteName: when declared(testSuiteName): testSuiteName else: "", testName: name, @@ -540,7 +540,7 @@ template fail* = when declared(testStatusIMPL): testStatusIMPL = FAILED else: - programResult += 1 + programResult = 1 ensureInitialized() -- cgit 1.4.1-2-gfad0 From 0a2f711b9e91ae0b188bc9616598d2fcbd8b29b3 Mon Sep 17 00:00:00 2001 From: narimiran Date: Fri, 11 Jan 2019 08:51:19 +0100 Subject: revert moving `oids` and `smtp` to graveyard --- changelog.md | 5 +- doc/lib.rst | 9 ++ lib/pure/oids.nim | 93 +++++++++++++++++++ lib/pure/smtp.nim | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++ lib/pure/smtp.nim.cfg | 1 + tools/kochdocs.nim | 2 + 6 files changed, 361 insertions(+), 2 deletions(-) create mode 100644 lib/pure/oids.nim create mode 100644 lib/pure/smtp.nim create mode 100644 lib/pure/smtp.nim.cfg diff --git a/changelog.md b/changelog.md index da06f3daa..2ac668cc6 100644 --- a/changelog.md +++ b/changelog.md @@ -62,8 +62,9 @@ - several deprecated modules were removed: `ssl`, `matchers`, `httpserver`, `unsigned`, `actors`, `parseurl` -- several poorly documented and not used modules were moved to graveyard - (they are available as Nimble packages): `subexes`, `scgi`, `smtp`, `oids` +- two poorly documented and not used modules (`subexes`, `scgi`) were moved to + graveyard (they are available as Nimble packages) + #### Breaking changes in the compiler diff --git a/doc/lib.rst b/doc/lib.rst index d6c296e8d..6b1af43e1 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -238,6 +238,9 @@ Internet Protocols and Support This module implements a simple HTTP client which supports both synchronous and asynchronous retrieval of web pages. +* `smtp `_ + This module implement a simple SMTP client. + * `cookies `_ This module contains helper procs for parsing and generating cookies. @@ -372,6 +375,12 @@ Multimedia support Miscellaneous ------------- +* `oids `_ + An OID is a global ID that consists of a timestamp, + a unique counter and a random value. This combination should suffice to + produce a globally distributed unique ID. This implementation was extracted + from the Mongodb interface and it thus binary compatible with a Mongo OID. + * `endians `_ This module contains helpers that deal with different byte orders. diff --git a/lib/pure/oids.nim b/lib/pure/oids.nim new file mode 100644 index 000000000..d6369b5f9 --- /dev/null +++ b/lib/pure/oids.nim @@ -0,0 +1,93 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2013 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Nim OID support. An OID is a global ID that consists of a timestamp, +## a unique counter and a random value. This combination should suffice to +## produce a globally distributed unique ID. This implementation was extracted +## from the Mongodb interface and it thus binary compatible with a Mongo OID. +## +## This implementation calls ``math.randomize()`` for the first call of +## ``genOid``. + +import times, endians + +type + Oid* = object ## an OID + time: int32 ## + fuzz: int32 ## + count: int32 ## + +proc `==`*(oid1: Oid, oid2: Oid): bool = + ## Compare two Mongo Object IDs for equality + return (oid1.time == oid2.time) and (oid1.fuzz == oid2.fuzz) and (oid1.count == oid2.count) + +proc hexbyte*(hex: char): int = + case hex + of '0'..'9': result = (ord(hex) - ord('0')) + of 'a'..'f': result = (ord(hex) - ord('a') + 10) + of 'A'..'F': result = (ord(hex) - ord('A') + 10) + else: discard + +proc parseOid*(str: cstring): Oid = + ## parses an OID. + var bytes = cast[cstring](addr(result.time)) + var i = 0 + while i < 12: + bytes[i] = chr((hexbyte(str[2 * i]) shl 4) or hexbyte(str[2 * i + 1])) + inc(i) + +proc oidToString*(oid: Oid, str: cstring) = + const hex = "0123456789abcdef" + # work around a compiler bug: + var str = str + var o = oid + var bytes = cast[cstring](addr(o)) + var i = 0 + while i < 12: + let b = bytes[i].ord + str[2 * i] = hex[(b and 0xF0) shr 4] + str[2 * i + 1] = hex[b and 0xF] + inc(i) + str[24] = '\0' + +proc `$`*(oid: Oid): string = + result = newString(24) + oidToString(oid, result) + +var + incr: int + fuzz: int32 + +proc genOid*(): Oid = + ## generates a new OID. + proc rand(): cint {.importc: "rand", header: "", nodecl.} + proc srand(seed: cint) {.importc: "srand", header: "", nodecl.} + + var t = getTime().toUnix.int32 + + var i = int32(atomicInc(incr)) + + if fuzz == 0: + # racy, but fine semantically: + srand(t) + fuzz = rand() + bigEndian32(addr result.time, addr(t)) + result.fuzz = fuzz + bigEndian32(addr result.count, addr(i)) + +proc generatedTime*(oid: Oid): Time = + ## returns the generated timestamp of the OID. + var tmp: int32 + var dummy = oid.time + bigEndian32(addr(tmp), addr(dummy)) + result = fromUnix(tmp) + +when not defined(testing) and isMainModule: + let xo = genOid() + echo xo.generatedTime diff --git a/lib/pure/smtp.nim b/lib/pure/smtp.nim new file mode 100644 index 000000000..5f4b09f80 --- /dev/null +++ b/lib/pure/smtp.nim @@ -0,0 +1,253 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2012 Dominik Picheta +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements the SMTP client protocol as specified by RFC 5321, +## this can be used to send mail to any SMTP Server. +## +## This module also implements the protocol used to format messages, +## as specified by RFC 2822. +## +## Example gmail use: +## +## +## .. code-block:: Nim +## var msg = createMessage("Hello from Nim's SMTP", +## "Hello!.\n Is this awesome or what?", +## @["foo@gmail.com"]) +## let smtpConn = newSmtp(useSsl = true, debug=true) +## smtpConn.connect("smtp.gmail.com", Port 465) +## smtpConn.auth("username", "password") +## smtpConn.sendmail("username@gmail.com", @["foo@gmail.com"], $msg) +## +## +## For SSL support this module relies on OpenSSL. If you want to +## enable SSL, compile with ``-d:ssl``. + +import net, strutils, strtabs, base64, os +import asyncnet, asyncdispatch + +export Port + +type + Message* = object + msgTo: seq[string] + msgCc: seq[string] + msgSubject: string + msgOtherHeaders: StringTableRef + msgBody: string + + ReplyError* = object of IOError + + SmtpBase[SocketType] = ref object + sock: SocketType + debug: bool + + Smtp* = SmtpBase[Socket] + AsyncSmtp* = SmtpBase[AsyncSocket] + +proc debugSend(smtp: Smtp | AsyncSmtp, cmd: string) {.multisync.} = + if smtp.debug: + echo("C:" & cmd) + await smtp.sock.send(cmd) + +proc debugRecv(smtp: Smtp | AsyncSmtp): Future[TaintedString] {.multisync.} = + result = await smtp.sock.recvLine() + if smtp.debug: + echo("S:" & result.string) + +proc quitExcpt(smtp: Smtp, msg: string) = + smtp.debugSend("QUIT") + raise newException(ReplyError, msg) + +const compiledWithSsl = defined(ssl) + +when not defined(ssl): + type PSSLContext = ref object + let defaultSSLContext: PSSLContext = nil +else: + var defaultSSLContext {.threadvar.}: SSLContext + + proc getSSLContext(): SSLContext = + if defaultSSLContext == nil: + defaultSSLContext = newContext(verifyMode = CVerifyNone) + result = defaultSSLContext + +proc createMessage*(mSubject, mBody: string, mTo, mCc: seq[string], + otherHeaders: openarray[tuple[name, value: string]]): Message = + ## Creates a new MIME compliant message. + result.msgTo = mTo + result.msgCc = mCc + result.msgSubject = mSubject + result.msgBody = mBody + result.msgOtherHeaders = newStringTable() + for n, v in items(otherHeaders): + result.msgOtherHeaders[n] = v + +proc createMessage*(mSubject, mBody: string, mTo, + mCc: seq[string] = @[]): Message = + ## Alternate version of the above. + result.msgTo = mTo + result.msgCc = mCc + result.msgSubject = mSubject + result.msgBody = mBody + result.msgOtherHeaders = newStringTable() + +proc `$`*(msg: Message): string = + ## stringify for ``Message``. + result = "" + if msg.msgTo.len() > 0: + result = "TO: " & msg.msgTo.join(", ") & "\c\L" + if msg.msgCc.len() > 0: + result.add("CC: " & msg.msgCc.join(", ") & "\c\L") + # TODO: Folding? i.e when a line is too long, shorten it... + result.add("Subject: " & msg.msgSubject & "\c\L") + for key, value in pairs(msg.msgOtherHeaders): + result.add(key & ": " & value & "\c\L") + + result.add("\c\L") + result.add(msg.msgBody) + +proc newSmtp*(useSsl = false, debug=false, + sslContext: SSLContext = nil): Smtp = + ## Creates a new ``Smtp`` instance. + new result + result.debug = debug + result.sock = newSocket() + if useSsl: + when compiledWithSsl: + if sslContext == nil: + getSSLContext().wrapSocket(result.sock) + else: + sslContext.wrapSocket(result.sock) + else: + {.error: "SMTP module compiled without SSL support".} + +proc newAsyncSmtp*(useSsl = false, debug=false, + sslContext: SSLContext = nil): AsyncSmtp = + ## Creates a new ``AsyncSmtp`` instance. + new result + result.debug = debug + + result.sock = newAsyncSocket() + if useSsl: + when compiledWithSsl: + if sslContext == nil: + getSSLContext().wrapSocket(result.sock) + else: + sslContext.wrapSocket(result.sock) + else: + {.error: "SMTP module compiled without SSL support".} + +proc quitExcpt(smtp: AsyncSmtp, msg: string): Future[void] = + var retFuture = newFuture[void]() + var sendFut = smtp.debugSend("QUIT") + sendFut.callback = + proc () = + # TODO: Fix this in async procs. + raise newException(ReplyError, msg) + return retFuture + +proc checkReply(smtp: Smtp | AsyncSmtp, reply: string) {.multisync.} = + var line = await smtp.debugRecv() + if not line.startswith(reply): + await quitExcpt(smtp, "Expected " & reply & " reply, got: " & line) + +proc connect*(smtp: Smtp | AsyncSmtp, + address: string, port: Port) {.multisync.} = + ## Establishes a connection with a SMTP server. + ## May fail with ReplyError or with a socket error. + await smtp.sock.connect(address, port) + + await smtp.checkReply("220") + await smtp.debugSend("HELO " & address & "\c\L") + await smtp.checkReply("250") + +proc auth*(smtp: Smtp | AsyncSmtp, username, password: string) {.multisync.} = + ## Sends an AUTH command to the server to login as the `username` + ## using `password`. + ## May fail with ReplyError. + + await smtp.debugSend("AUTH LOGIN\c\L") + await smtp.checkReply("334") # TODO: Check whether it's asking for the "Username:" + # i.e "334 VXNlcm5hbWU6" + await smtp.debugSend(encode(username) & "\c\L") + await smtp.checkReply("334") # TODO: Same as above, only "Password:" (I think?) + + await smtp.debugSend(encode(password) & "\c\L") + await smtp.checkReply("235") # Check whether the authentification was successful. + +proc sendMail*(smtp: Smtp | AsyncSmtp, fromAddr: string, + toAddrs: seq[string], msg: string) {.multisync.} = + ## Sends ``msg`` from ``fromAddr`` to the addresses specified in ``toAddrs``. + ## Messages may be formed using ``createMessage`` by converting the + ## Message into a string. + + await smtp.debugSend("MAIL FROM:<" & fromAddr & ">\c\L") + await smtp.checkReply("250") + for address in items(toAddrs): + await smtp.debugSend("RCPT TO:<" & address & ">\c\L") + await smtp.checkReply("250") + + # Send the message + await smtp.debugSend("DATA " & "\c\L") + await smtp.checkReply("354") + await smtp.sock.send(msg & "\c\L") + await smtp.debugSend(".\c\L") + await smtp.checkReply("250") + +proc close*(smtp: Smtp | AsyncSmtp) {.multisync.} = + ## Disconnects from the SMTP server and closes the socket. + await smtp.debugSend("QUIT\c\L") + smtp.sock.close() + +when not defined(testing) and isMainModule: + # To test with a real SMTP service, create a smtp.ini file, e.g.: + # username = "" + # password = "" + # smtphost = "smtp.gmail.com" + # port = 465 + # use_tls = true + # sender = "" + # recipient = "" + + import parsecfg + + proc `[]`(c: Config, key: string): string = c.getSectionValue("", key) + + let + conf = loadConfig("smtp.ini") + msg = createMessage("Hello from Nim's SMTP!", + "Hello!\n Is this awesome or what?", @[conf["recipient"]]) + + assert conf["smtphost"] != "" + + proc async_test() {.async.} = + let client = newAsyncSmtp( + conf["use_tls"].parseBool, + debug=true + ) + await client.connect(conf["smtphost"], conf["port"].parseInt.Port) + await client.auth(conf["username"], conf["password"]) + await client.sendMail(conf["sender"], @[conf["recipient"]], $msg) + await client.close() + echo "async email sent" + + proc sync_test() = + var smtpConn = newSmtp( + conf["use_tls"].parseBool, + debug=true + ) + smtpConn.connect(conf["smtphost"], conf["port"].parseInt.Port) + smtpConn.auth(conf["username"], conf["password"]) + smtpConn.sendMail(conf["sender"], @[conf["recipient"]], $msg) + smtpConn.close() + echo "sync email sent" + + waitFor async_test() + sync_test() diff --git a/lib/pure/smtp.nim.cfg b/lib/pure/smtp.nim.cfg new file mode 100644 index 000000000..521e21de4 --- /dev/null +++ b/lib/pure/smtp.nim.cfg @@ -0,0 +1 @@ +-d:ssl diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim index b348d3172..3d146188f 100644 --- a/tools/kochdocs.nim +++ b/tools/kochdocs.nim @@ -161,6 +161,7 @@ lib/impure/db_sqlite.nim lib/impure/db_odbc.nim lib/pure/db_common.nim lib/pure/httpclient.nim +lib/pure/smtp.nim lib/pure/ropes.nim lib/pure/unidecode/unidecode.nim lib/pure/xmlparser.nim @@ -190,6 +191,7 @@ lib/pure/memfiles.nim lib/pure/collections/critbits.nim lib/core/locks.nim lib/core/rlocks.nim +lib/pure/oids.nim lib/pure/endians.nim lib/pure/uri.nim lib/pure/nimprof.nim -- cgit 1.4.1-2-gfad0 From ceabbeadd7807dd21f274271acc779a7a8e69132 Mon Sep 17 00:00:00 2001 From: Jörg Wollenschläger Date: Fri, 11 Jan 2019 20:07:46 +0900 Subject: Fixes new atomics for C + vcc (#10267) --- lib/pure/concurrency/atomics.nim | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/pure/concurrency/atomics.nim b/lib/pure/concurrency/atomics.nim index 5a3709f87..9e716bdf4 100644 --- a/lib/pure/concurrency/atomics.nim +++ b/lib/pure/concurrency/atomics.nim @@ -136,7 +136,7 @@ else: # A type that is known to be atomic and whose size is known at # compile time to be 8 bytes or less - template nonAtomicType(T: typedesc[Trivial]): typedesc = + template nonAtomicType(T: typedesc[Trivial]): untyped = # Maps types to integers of the same size when sizeof(T) == 1: int8 elif sizeof(T) == 2: int16 @@ -204,33 +204,33 @@ else: proc clear*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent) = discard interlockedAnd(addr(location), 0'i8) - proc load*[T: Trivial](location: var T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = - cast[T](interlockedOr(addr(location), (nonAtomicType(T))0)) - proc store*[T: Trivial](location: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.inline.} = - discard interlockedExchange(addr(location), cast[nonAtomicType(T)](desired)) + proc load*[T: Trivial](location: var Atomic[T]; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedOr(addr(location.value), (nonAtomicType(T))0)) + proc store*[T: Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.inline.} = + discard interlockedExchange(addr(location.value), cast[nonAtomicType(T)](desired)) - proc exchange*[T: Trivial](location: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = - cast[T](interlockedExchange(addr(location), cast[int64](desired))) - proc compareExchange*[T: Trivial](location: var T; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = - cast[T](interlockedCompareExchange(addr(location), cast[nonAtomicType(T)](desired), cast[nonAtomicType(T)](expected))) == expected - proc compareExchange*[T: Trivial](location: var T; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = + proc exchange*[T: Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedExchange(addr(location.value), cast[int64](desired))) + proc compareExchange*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = + cast[T](interlockedCompareExchange(addr(location.value), cast[nonAtomicType(T)](desired), cast[nonAtomicType(T)](expected))) == expected + proc compareExchange*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = compareExchange(location, expected, desired, order, order) - proc compareExchangeWeak*[T: Trivial](location: var T; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = + proc compareExchangeWeak*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = compareExchange(location, expected, desired, success, failure) - proc compareExchangeWeak*[T: Trivial](location: var T; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = + proc compareExchangeWeak*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = compareExchangeWeak(location, expected, desired, order, order) - proc fetchAdd*[T: SomeInteger](location: var T; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = - let currentValue = location.load() + proc fetchAdd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + var currentValue = location.load() while not compareExchangeWeak(location, currentValue, currentValue + value): discard - proc fetchSub*[T: SomeInteger](location: var T; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + proc fetchSub*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = fetchAdd(location, -value, order) - proc fetchAnd*[T: SomeInteger](location: var T; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = - cast[T](interlockedAnd(addr(location), cast[nonAtomicType(T)](value))) - proc fetchOr*[T: SomeInteger](location: var T; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = - cast[T](interlockedOr(addr(location), cast[nonAtomicType(T)](value))) - proc fetchXor*[T: SomeInteger](location: var T; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = - cast[T](interlockedXor(addr(location), cast[nonAtomicType(T)](value))) + proc fetchAnd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedAnd(addr(location.value), cast[nonAtomicType(T)](value))) + proc fetchOr*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedOr(addr(location.value), cast[nonAtomicType(T)](value))) + proc fetchXor*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedXor(addr(location.value), cast[nonAtomicType(T)](value))) else: {.push, header: "".} -- cgit 1.4.1-2-gfad0 From d1b7aa28e7b5b847a44eda11ff7aadbe3bb0b49a Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Fri, 11 Jan 2019 15:55:16 +0100 Subject: fixes #10240 (#10269) * kochdocs.nim: code cleanup * fixes #10420 --- compiler/scriptconfig.nim | 6 ++++-- tools/kochdocs.nim | 6 +----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim index bfff86479..e3a9478d1 100644 --- a/compiler/scriptconfig.nim +++ b/compiler/scriptconfig.nim @@ -63,8 +63,10 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; os.removeFile getString(a, 0) cbos createDir: os.createDir getString(a, 0) - cbos getOsError: - setResult(a, errorMsg) + + result.registerCallback "stdlib.system.getOsError", + proc (a: VmArgs) = setResult(a, errorMsg) + cbos setCurrentDir: os.setCurrentDir getString(a, 0) cbos getCurrentDir: diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim index 3d146188f..68662bab2 100644 --- a/tools/kochdocs.nim +++ b/tools/kochdocs.nim @@ -56,12 +56,8 @@ proc nimexec*(cmd: string) = exec findNim() & " " & cmd proc nimCompile*(input: string, outputDir = "bin", mode = "c", options = "") = - # TODO: simplify pending https://github.com/nim-lang/Nim/issues/9513 - var cmd = findNim() & " " & mode let output = outputDir / input.splitFile.name.exe - cmd.add " -o:" & output - cmd.add " " & options - cmd.add " " & input + let cmd = findNim() & " " & mode & " -o:" & output & " " & options & " " & input exec cmd const -- cgit 1.4.1-2-gfad0 From 5397c5d31d58f14ad39e8009882a87ceda6253aa Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Fri, 11 Jan 2019 16:34:22 +0100 Subject: fixes #10216 (#10270) --- compiler/pragmas.nim | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index bb5707cd5..b1a88ace7 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -64,10 +64,10 @@ const varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl, wMagic, wHeader, wDeprecated, wCompilerProc, wCore, wDynlib, wExtern, wImportCpp, wImportObjC, wError, wNoInit, wCompileTime, wGlobal, - wGensym, wInject, wCodegenDecl, wGuard, wGoto, wExportNims, wUsed, wRaises} + wGensym, wInject, wCodegenDecl, wGuard, wGoto, wExportNims, wUsed} constPragmas* = {wImportc, wExportc, wHeader, wDeprecated, wMagic, wNodecl, wExtern, wImportCpp, wImportObjC, wError, wGensym, wInject, wExportNims, - wIntDefine, wStrDefine, wUsed, wCompilerProc, wCore, wRaises} + wIntDefine, wStrDefine, wUsed, wCompilerProc, wCore} letPragmas* = varPragmas procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNosideeffect, wThread, wRaises, wLocks, wTags, wGcSafe} @@ -1119,13 +1119,16 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, else: sym.flags.incl sfUsed of wLiftLocals: discard else: invalidPragma(c, it) - elif sym == nil or (sym != nil and sym.kind in {skVar, skLet, skParam, - skField, skProc, skFunc, skConverter, skMethod, skType}): - n.sons[i] = semCustomPragma(c, it) - elif sym != nil: - illegalCustomPragma(c, it, sym) + elif comesFromPush and whichKeyword(ident) in {wTags, wRaises}: + discard "ignore the .push pragma; it doesn't apply" else: - invalidPragma(c, it) + if sym == nil or (sym != nil and sym.kind in {skVar, skLet, skParam, + skField, skProc, skFunc, skConverter, skMethod, skType}): + n.sons[i] = semCustomPragma(c, it) + elif sym != nil: + illegalCustomPragma(c, it, sym) + else: + invalidPragma(c, it) proc mergePragmas(n, pragmas: PNode) = if n[pragmasPos].kind == nkEmpty: -- cgit 1.4.1-2-gfad0 From 09ef25810aa7de55c32dfe7025668f1f889d00d6 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Fri, 11 Jan 2019 08:03:19 -0800 Subject: refs #10249 ; more debug info to diagnose failures (#10266) --- testament/tester.nim | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/testament/tester.nim b/testament/tester.nim index 1867871c5..04552b7c9 100644 --- a/testament/tester.nim +++ b/testament/tester.nim @@ -104,15 +104,19 @@ proc execCmdEx2(command: string, args: openarray[string], options: set[ProcessOp var line = newStringOfCap(120).TaintedString while true: if outp.readLine(line): - result[0].string.add(line.string) - result[0].string.add("\n") + result.output.string.add(line.string) + result.output.string.add("\n") if onStdout != nil: onStdout(line.string) else: - result[1] = peekExitCode(p) - if result[1] != -1: break + result.exitCode = peekExitCode(p) + if result.exitCode != -1: break close(p) - - + if result.exitCode != 0: + var command2 = command + if args.len > 0: command2.add " " & args.quoteShellCommand + echo (msg: "execCmdEx2 failed", + command: command2, + options: options) proc nimcacheDir(filename, options: string, target: TTarget): string = ## Give each test a private nimcache dir so they don't clobber each other's. -- cgit 1.4.1-2-gfad0 From b0979c8b1c3b43c285fc06ba492b20607e264560 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Fri, 11 Jan 2019 23:46:03 +0530 Subject: Cleanup comesFromPush logic (#10278) --- compiler/pragmas.nim | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index b1a88ace7..4adf6032b 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -1054,14 +1054,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, noVal(c, it) if sym == nil: invalidPragma(c, it) of wLine: pragmaLine(c, it) - of wRaises, wTags: - if not sym.isNil and sym.kind in {skVar, skLet, skConst}: - if comesFromPush: - return - else: - invalidPragma(c, it) - else: - pragmaRaisesOrTags(c, it) + of wRaises, wTags: pragmaRaisesOrTags(c, it) of wLocks: if sym == nil: pragmaLockStmt(c, it) elif sym.typ == nil: invalidPragma(c, it) -- cgit 1.4.1-2-gfad0 From c3d80647aec6b09673e452d9f451e0549bb10dbc Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 11 Jan 2019 12:52:31 +0100 Subject: docgen: render 'interpreted text' the same as inline literals for markdown compat --- lib/packages/docutils/rstgen.nim | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/packages/docutils/rstgen.nim b/lib/packages/docutils/rstgen.nim index 232da5c93..766ce6ffd 100644 --- a/lib/packages/docutils/rstgen.nim +++ b/lib/packages/docutils/rstgen.nim @@ -1134,11 +1134,9 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) = of rnTripleEmphasis: renderAux(d, n, "$1", "\\textbf{emph{$1}}", result) - of rnInterpretedText: - renderAux(d, n, "$1", "\\emph{$1}", result) of rnIdx: renderIndexTerm(d, n, result) - of rnInlineLiteral: + of rnInlineLiteral, rnInterpretedText: renderAux(d, n, "$1", "\\texttt{$1}", result) -- cgit 1.4.1-2-gfad0 From 5ef5dc86c523c760b83050b6192f00b6a0816bce Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 11 Jan 2019 14:20:34 +0100 Subject: docgen: support markdown link syntax; enable markdown extensions --- compiler/docgen.nim | 4 ++-- lib/packages/docutils/rst.nim | 28 ++++++++++++++++++++++++++++ lib/packages/docutils/rstast.nim | 3 +++ nimdoc/tester.nim | 2 +- nimdoc/testproject/expected/testproject.html | 2 +- nimdoc/testproject/subdir/subdir_b/utils.nim | 2 ++ 6 files changed, 37 insertions(+), 4 deletions(-) diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 1af9c06b8..33cd98f38 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -119,7 +119,7 @@ proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef, result.conf = conf result.cache = cache initRstGenerator(result[], (if conf.cmd != cmdRst2tex: outHtml else: outLatex), - conf.configVars, filename.string, {roSupportRawDirective}, + conf.configVars, filename.string, {roSupportRawDirective, roSupportMarkdown}, docgenFindFile, compilerMsgHandler) if conf.configVars.hasKey("doc.googleAnalytics"): @@ -991,7 +991,7 @@ proc commandRstAux(cache: IdentCache, conf: ConfigRef; d.isPureRst = true var rst = parseRst(readFile(filen.string), filen.string, 0, 1, d.hasToc, - {roSupportRawDirective}, conf) + {roSupportRawDirective, roSupportMarkdown}, conf) var modDesc = newStringOfCap(30_000) renderRstToOut(d[], rst, modDesc) d.modDesc = rope(modDesc) diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index 161509afe..6a03e6bc1 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -780,6 +780,31 @@ proc parseMarkdownCodeblock(p: var RstParser): PRstNode = add(result, nil) add(result, lb) +proc parseMarkdownLink(p: var RstParser; father: PRstNode): bool = + result = true + var desc, link = "" + var i = p.idx + + template parse(endToken, dest) = + inc i # skip begin token + while true: + if p.tok[i].kind in {tkEof, tkIndent}: return false + if p.tok[i].symbol == endToken: break + dest.add p.tok[i].symbol + inc i + inc i # skip end token + + parse("]", desc) + if p.tok[i].symbol != "(": return false + parse(")", link) + let child = newRstNode(rnHyperlink) + child.add desc + child.add link + # only commit if we detected no syntax error: + father.add child + p.idx = i + result = true + proc parseInline(p: var RstParser, father: PRstNode) = case p.tok[p.idx].kind of tkPunct: @@ -811,6 +836,9 @@ proc parseInline(p: var RstParser, father: PRstNode) = var n = newRstNode(rnSubstitutionReferences) parseUntil(p, n, "|", false) add(father, n) + elif roSupportMarkdown in p.s.options and p.tok[p.idx].symbol == "[" and + parseMarkdownLink(p, father): + discard "parseMarkdownLink already processed it" else: if roSupportSmilies in p.s.options: let n = parseSmiley(p) diff --git a/lib/packages/docutils/rstast.nim b/lib/packages/docutils/rstast.nim index 4a77b4f34..fee824b09 100644 --- a/lib/packages/docutils/rstast.nim +++ b/lib/packages/docutils/rstast.nim @@ -89,6 +89,9 @@ proc lastSon*(n: PRstNode): PRstNode = proc add*(father, son: PRstNode) = add(father.sons, son) +proc add*(father: PRstNode; s: string) = + add(father.sons, newRstNode(rnLeaf, s)) + proc addIfNotNil*(father, son: PRstNode) = if son != nil: add(father, son) diff --git a/nimdoc/tester.nim b/nimdoc/tester.nim index e0afe6b94..db2095128 100644 --- a/nimdoc/tester.nim +++ b/nimdoc/tester.nim @@ -28,5 +28,5 @@ proc test(dir: string; fixup = false) = echo "SUCCESS: files identical: ", produced removeDir(dir / "htmldocs") -test("nimdoc/testproject", false) +test("nimdoc/testproject", defined(fixup)) if failures > 0: quit($failures & " failures occurred.") diff --git a/nimdoc/testproject/expected/testproject.html b/nimdoc/testproject/expected/testproject.html index 2e23a64d5..dd1541a56 100644 --- a/nimdoc/testproject/expected/testproject.html +++ b/nimdoc/testproject/expected/testproject.html @@ -1369,7 +1369,7 @@ The enum B.
func someFunc() {...}{.raises: [], tags: [].}
-My someFunc. +My someFunc. Stuff in quotes here. Some link
diff --git a/nimdoc/testproject/subdir/subdir_b/utils.nim b/nimdoc/testproject/subdir/subdir_b/utils.nim index e2ec80dc2..7b529a617 100644 --- a/nimdoc/testproject/subdir/subdir_b/utils.nim +++ b/nimdoc/testproject/subdir/subdir_b/utils.nim @@ -23,4 +23,6 @@ template bEnum*(): untyped = func someFunc*() = ## My someFunc. + ## Stuff in `quotes` here. + ## [Some link](https://nim-lang.org) discard -- cgit 1.4.1-2-gfad0 From ee7f2d97cc5e07af3f73a1a8e93086f2e86385bc Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 11 Jan 2019 15:06:45 +0100 Subject: docgen: support markdown headings too --- lib/packages/docutils/rst.nim | 39 +++++++++++++++++----- .../expected/subdir/subdir_b/utils.html | 14 ++++++-- nimdoc/testproject/expected/theindex.html | 2 +- nimdoc/testproject/subdir/subdir_b/utils.nim | 13 ++++++++ 4 files changed, 57 insertions(+), 11 deletions(-) diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index 6a03e6bc1..25cea5943 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -1065,15 +1065,32 @@ proc isOptionList(p: RstParser): bool = result = match(p, p.idx, "-w") or match(p, p.idx, "--w") or match(p, p.idx, "/w") or match(p, p.idx, "//w") +proc isMarkdownHeadlinePattern(s: string): bool = + if s.len >= 1 and s.len <= 6: + for c in s: + if c != '#': return false + result = true + +proc isMarkdownHeadline(p: RstParser): bool = + if roSupportMarkdown in p.s.options: + if isMarkdownHeadlinePattern(p.tok[p.idx].symbol) and p.tok[p.idx+1].kind == tkWhite: + if p.tok[p.idx+2].kind in {tkWord, tkOther, tkPunct}: + result = true + proc whichSection(p: RstParser): RstNodeKind = case p.tok[p.idx].kind of tkAdornment: if match(p, p.idx + 1, "ii"): result = rnTransition elif match(p, p.idx + 1, " a"): result = rnTable elif match(p, p.idx + 1, "i"): result = rnOverline - else: result = rnLeaf + elif isMarkdownHeadline(p): + result = rnHeadline + else: + result = rnLeaf of tkPunct: - if match(p, tokenAfterNewline(p), "ai"): + if isMarkdownHeadline(p): + result = rnHeadline + elif match(p, tokenAfterNewline(p), "ai"): result = rnHeadline elif p.tok[p.idx].symbol == "::": result = rnLiteralBlock @@ -1158,12 +1175,18 @@ proc parseParagraph(p: var RstParser, result: PRstNode) = proc parseHeadline(p: var RstParser): PRstNode = result = newRstNode(rnHeadline) - parseUntilNewline(p, result) - assert(p.tok[p.idx].kind == tkIndent) - assert(p.tok[p.idx + 1].kind == tkAdornment) - var c = p.tok[p.idx + 1].symbol[0] - inc(p.idx, 2) - result.level = getLevel(p.s.underlineToLevel, p.s.uLevel, c) + if isMarkdownHeadline(p): + result.level = p.tok[p.idx].symbol.len + assert(p.tok[p.idx+1].kind == tkWhite) + inc p.idx, 2 + parseUntilNewline(p, result) + else: + parseUntilNewline(p, result) + assert(p.tok[p.idx].kind == tkIndent) + assert(p.tok[p.idx + 1].kind == tkAdornment) + var c = p.tok[p.idx + 1].symbol[0] + inc(p.idx, 2) + result.level = getLevel(p.s.underlineToLevel, p.s.uLevel, c) type IntSeq = seq[int] diff --git a/nimdoc/testproject/expected/subdir/subdir_b/utils.html b/nimdoc/testproject/expected/subdir/subdir_b/utils.html index 2c89acce4..f2f626459 100644 --- a/nimdoc/testproject/expected/subdir/subdir_b/utils.html +++ b/nimdoc/testproject/expected/subdir/subdir_b/utils.html @@ -1238,7 +1238,12 @@ function main() {
    -
  • +
  • This is now a header
  • +
  • More headers
  • +
  • Types
    • -

      +

      +

      This is now a header

      +

      Next header

      +

      And so on

      +

      More headers

      +
      Up to level 6

      Types

      diff --git a/nimdoc/testproject/expected/theindex.html b/nimdoc/testproject/expected/theindex.html index 652077be8..95c667acc 100644 --- a/nimdoc/testproject/expected/theindex.html +++ b/nimdoc/testproject/expected/theindex.html @@ -1220,7 +1220,7 @@ function main() {

      Index

      - Modules: subdir/subdir_b/utils, testproject.

      API symbols

      + Modules: testproject, utils.

      API symbols

      A:
      • testproject: A
      • diff --git a/nimdoc/testproject/subdir/subdir_b/utils.nim b/nimdoc/testproject/subdir/subdir_b/utils.nim index 7b529a617..28c00c755 100644 --- a/nimdoc/testproject/subdir/subdir_b/utils.nim +++ b/nimdoc/testproject/subdir/subdir_b/utils.nim @@ -1,3 +1,16 @@ +##[ + +# This is now a header + +## Next header + +### And so on + +# More headers + +###### Up to level 6 + +]## type SomeType* = enum -- cgit 1.4.1-2-gfad0 From 56b804a283a69baa4887eaf300c353a5748f86a4 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 11 Jan 2019 16:31:24 +0100 Subject: RST parser: fixes #8158 --- lib/packages/docutils/rst.nim | 6 +++--- nimdoc/testproject/expected/subdir/subdir_b/utils.html | 9 ++++++++- nimdoc/testproject/subdir/subdir_b/utils.nim | 9 +++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index 25cea5943..551df9919 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -560,9 +560,9 @@ proc match(p: RstParser, start: int, expr: string): bool = result = (p.tok[j].kind == tkWord) or (p.tok[j].symbol == "#") if result: case p.tok[j].symbol[0] - of 'a'..'z', 'A'..'Z': result = len(p.tok[j].symbol) == 1 + of 'a'..'z', 'A'..'Z', '#': result = len(p.tok[j].symbol) == 1 of '0'..'9': result = allCharsInSet(p.tok[j].symbol, {'0'..'9'}) - else: discard + else: result = false else: var c = expr[i] var length = 0 @@ -1105,7 +1105,7 @@ proc whichSection(p: RstParser): RstNodeKind = elif match(p, p.idx, ":w:") and predNL(p): # (p.tok[p.idx].symbol == ":") result = rnFieldList - elif match(p, p.idx, "(e) "): + elif match(p, p.idx, "(e) ") or match(p, p.idx, "e. "): result = rnEnumList elif match(p, p.idx, "+a+"): result = rnGridTable diff --git a/nimdoc/testproject/expected/subdir/subdir_b/utils.html b/nimdoc/testproject/expected/subdir/subdir_b/utils.html index f2f626459..9ba3c24e1 100644 --- a/nimdoc/testproject/expected/subdir/subdir_b/utils.html +++ b/nimdoc/testproject/expected/subdir/subdir_b/utils.html @@ -1281,7 +1281,14 @@ function main() {

        Next header

        And so on

        More headers

        -
        Up to level 6

        +
        Up to level 6
        1. An enumeration
        2. +
        3. Second idea here.
        4. +
        +

        More text.

        +
        1. Other case value
        2. +
        3. Second case.
        4. +
        +

        Types

        diff --git a/nimdoc/testproject/subdir/subdir_b/utils.nim b/nimdoc/testproject/subdir/subdir_b/utils.nim index 28c00c755..0576f194f 100644 --- a/nimdoc/testproject/subdir/subdir_b/utils.nim +++ b/nimdoc/testproject/subdir/subdir_b/utils.nim @@ -10,6 +10,15 @@ ###### Up to level 6 + +#. An enumeration +#. Second idea here. + +More text. + +1. Other case value +2. Second case. + ]## type -- cgit 1.4.1-2-gfad0 From 647066e378bdbc85646dd88ea90eb15eddbbbc50 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 11 Jan 2019 18:36:20 +0100 Subject: make the stdlib work with the changed docgen --- doc/lib.rst | 2 +- lib/core/macros.nim | 2 +- lib/impure/nre.nim | 8 ++++---- lib/pure/collections/critbits.nim | 2 +- lib/pure/collections/sets.nim | 2 +- lib/pure/streams.nim | 2 +- lib/system/gc_common.nim | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/lib.rst b/doc/lib.rst index 6b1af43e1..395b242e6 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -108,7 +108,7 @@ String handling * `strformat `_ Macro based standard string interpolation / formatting. Inpired by - Python's ```f``-strings. + Python's ``f``-strings. * `strmisc `_ This module contains uncommon string handling operations that do not diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 64334161e..92a35193f 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -151,7 +151,7 @@ proc `==`*(a, b: NimNode): bool {.magic: "EqNimrodNode", noSideEffect.} proc `==`*(a, b: NimSym): bool {.magic: "EqNimrodNode", noSideEffect, deprecated.} ## compares two Nim symbols - ## **Deprecated since version 0.18.1**; Use ```==`(NimNode,NimNode)`` instead. + ## **Deprecated since version 0.18.1**; Use ``==(NimNode, NimNode)`` instead. proc sameType*(a, b: NimNode): bool {.magic: "SameNodeType", noSideEffect.} = diff --git a/lib/impure/nre.nim b/lib/impure/nre.nim index 94dd89db5..5c5125ba1 100644 --- a/lib/impure/nre.nim +++ b/lib/impure/nre.nim @@ -540,7 +540,7 @@ proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Opt raise RegexInternalError(msg : "Unknown internal error: " & $execRet) proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] = - ## Like ```find(...)`` <#proc-find>`_, but anchored to the start of the + ## Like ` ``find(...)`` <#proc-find>`_, but anchored to the start of the ## string. ## runnableExamples: @@ -550,11 +550,11 @@ proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[R return str.matchImpl(pattern, start, endpos, pcre.ANCHORED) iterator findIter*(str: string, pattern: Regex, start = 0, endpos = int.high): RegexMatch = - ## Works the same as ```find(...)`` <#proc-find>`_, but finds every + ## Works the same as ` ``find(...)`` <#proc-find>`_, but finds every ## non-overlapping match. ``"2222".find(re"22")`` is ``"22", "22"``, not ## ``"22", "22", "22"``. ## - ## Arguments are the same as ```find(...)`` <#proc-find>`_ + ## Arguments are the same as ` ``find(...)`` <#proc-find>`_ ## ## Variants: ## @@ -633,7 +633,7 @@ proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string] ## Splits the string with the given regex. This works according to the ## rules that Perl and Javascript use. ## - ## ``start`` behaves the same as in ```find(...)`` <#proc-find>`_. + ## ``start`` behaves the same as in ` ``find(...)`` <#proc-find>`_. ## runnableExamples: # - If the match is zero-width, then the string is still split: diff --git a/lib/pure/collections/critbits.nim b/lib/pure/collections/critbits.nim index 32e0299ba..1bd13920d 100644 --- a/lib/pure/collections/critbits.nim +++ b/lib/pure/collections/critbits.nim @@ -205,7 +205,7 @@ proc `[]`*[T](c: var CritBitTree[T], key: string): var T {.inline, proc mget*[T](c: var CritBitTree[T], key: string): var T {.inline, deprecated.} = ## retrieves the value at ``c[key]``. The value can be modified. ## If `key` is not in `t`, the ``KeyError`` exception is raised. - ## Use ```[]``` instead. + ## Use ``[]`` instead. get(c, key) iterator leaves[T](n: Node[T]): Node[T] = diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index 1273cbc33..07fcfe676 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -195,7 +195,7 @@ proc mget*[A](s: var HashSet[A], key: A): var A {.deprecated.} = ## returns the element that is actually stored in 's' which has the same ## value as 'key' or raises the ``KeyError`` exception. This is useful ## when one overloaded 'hash' and '==' but still needs reference semantics - ## for sharing. Use ```[]``` instead. + ## for sharing. Use ``[]`` instead. s[key] proc contains*[A](s: HashSet[A], key: A): bool = diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index b5254b4f7..10de86e9f 100644 --- a/lib/pure/streams.nim +++ b/lib/pure/streams.nim @@ -271,7 +271,7 @@ proc peekStr*(s: Stream, length: int): TaintedString = proc readLine*(s: Stream, line: var TaintedString): bool = ## reads a line of text from the stream `s` into `line`. `line` must not be ## ``nil``! May throw an IO exception. - ## A line of text may be delimited by ```LF`` or ``CRLF``. + ## A line of text may be delimited by ``LF`` or ``CRLF``. ## The newline character(s) are not part of the returned string. ## Returns ``false`` if the end of the file has been reached, ``true`` ## otherwise. If ``false`` is returned `line` contains no new data. diff --git a/lib/system/gc_common.nim b/lib/system/gc_common.nim index 565453298..ebd3dada2 100644 --- a/lib/system/gc_common.nim +++ b/lib/system/gc_common.nim @@ -418,7 +418,7 @@ proc prepareDealloc(cell: PCell) = decTypeSize(cell, t) proc deallocHeap*(runFinalizers = true; allowGcAfterwards = true) = - ## Frees the thread local heap. Runs every finalizer if ``runFinalizers``` + ## Frees the thread local heap. Runs every finalizer if ``runFinalizers`` ## is true. If ``allowGcAfterwards`` is true, a minimal amount of allocation ## happens to ensure the GC can continue to work after the call ## to ``deallocHeap``. -- cgit 1.4.1-2-gfad0 From 7e7603ed2b6774454689978a08b46adb2fb953da Mon Sep 17 00:00:00 2001 From: Ico Doornekamp Date: Sat, 12 Jan 2019 08:17:20 +0100 Subject: Documented error source argument of macros.error() (#10279) --- lib/core/macros.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 92a35193f..08ee05152 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -377,7 +377,9 @@ proc copyNimNode*(n: NimNode): NimNode {.magic: "NCopyNimNode", noSideEffect.} proc copyNimTree*(n: NimNode): NimNode {.magic: "NCopyNimTree", noSideEffect.} proc error*(msg: string, n: NimNode = nil) {.magic: "NError", benign.} - ## writes an error message at compile time + ## writes an error message at compile time. The optional ``n: NimNode`` + ## parameter is used as the source for file and line number information in + ## the compilation error message. proc warning*(msg: string, n: NimNode = nil) {.magic: "NWarning", benign.} ## writes a warning message at compile time -- cgit 1.4.1-2-gfad0 From 98ef545bedd27959a568a8e99f889a32c3ae1f72 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Fri, 11 Jan 2019 23:18:04 -0800 Subject: fix #10281 (#10282) --- lib/wrappers/openssl.nim | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim index 447592a68..9ee73a44d 100644 --- a/lib/wrappers/openssl.nim +++ b/lib/wrappers/openssl.nim @@ -38,7 +38,11 @@ when useWinVersion: from winlean import SocketHandle else: - const versions = "(.1.1|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|.46|.45|.44|.43|.41|.39|.38|.10|)" + when defined(osx): + # todo: find a better workaround for #10281 (caused by #10230) + const versions = "(.1.1|.38|.39|.41|.43|.44|.45|.46|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|)" + else: + const versions = "(.1.1|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|.46|.45|.44|.43|.41|.39|.38|.10|)" when defined(macosx): const -- cgit 1.4.1-2-gfad0 From f5cc2e2de567b36d33778ddf263b7a440f1d3b11 Mon Sep 17 00:00:00 2001 From: rec <44084068+recloser@users.noreply.github.com> Date: Sat, 12 Jan 2019 19:49:31 +0100 Subject: Fixes 10202 (#10283) * Add a test case for #10202 * Fix asgn for object tyVars; fixes #10202 * Check the variant kind before accessing the sym field --- compiler/jsgen.nim | 4 ++-- tests/js/t9410.nim | 37 +++++++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 2f5e202e0..8625f2fe1 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -925,7 +925,7 @@ const proc needsNoCopy(p: PProc; y: PNode): bool = return y.kind in nodeKindsNeedNoCopy or - ((mapType(y.typ) != etyBaseIndex or y.sym.kind == skParam) and + ((mapType(y.typ) != etyBaseIndex or (y.kind == nkSym and y.sym.kind == skParam)) and (skipTypes(y.typ, abstractInst).kind in {tyRef, tyPtr, tyLent, tyVar, tyCString, tyProc} + IntegralTypes)) @@ -950,7 +950,7 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = lineF(p, "$1 = nimCopy(null, $2, $3);$n", [a.rdLoc, b.res, genTypeInfo(p, y.typ)]) of etyObject: - if (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded: + if x.typ.kind == tyVar or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded: lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc]) else: useMagic(p, "nimCopy") diff --git a/tests/js/t9410.nim b/tests/js/t9410.nim index 9aca6d45b..78c329a24 100644 --- a/tests/js/t9410.nim +++ b/tests/js/t9410.nim @@ -1,13 +1,3 @@ -template doAssert(exp: untyped) = - when defined(echot9410): - let r = exp - echo $(instantiationInfo().line) & ":\n " & astToStr(exp) & "\n was " & repr(r) - when not defined(noassertt9410): - system.doAssert r - else: - when not defined(noassertt9410): - system.doAssert exp - template tests = block: var i = 0 @@ -428,6 +418,33 @@ template tests = let xptr2 = cast[type(xptr)](p2) doAssert xptr == xptr2 + + block: # var types + block t10202: + type Point = object + x: float + y: float + + var points: seq[Point] + + points.add(Point(x:1, y:2)) + + for i, p in points.mpairs: + p.x += 1 + + doAssert points[0].x == 2 + + block: + var ints = @[1, 2, 3] + for i, val in mpairs ints: + val *= 10 + doAssert ints == @[10, 20, 30] + + block: + var seqOfSeqs = @[@[1, 2], @[3, 4]] + for i, val in mpairs seqOfSeqs: + val[0] *= 10 + doAssert seqOfSeqs == @[@[10, 2], @[30, 4]] when false: block: # openarray -- cgit 1.4.1-2-gfad0 From 9af85fb69f26dcae5fba7881a597d78f6bc7ffc8 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Sun, 13 Jan 2019 00:00:39 -0800 Subject: fixes #10273 execShellCmd now returns nonzero when child killed with signal + other fixes (#10274) * s/exitStatus(...)/exitStatusLikeShell(...)/ * fix #10273 execShellCmd now returns nonzero when child exits with signal * test case for #10249 and explanation for the bug * fix test failure * add tests/nim.cfg --- .gitignore | 2 +- lib/pure/os.nim | 21 ++++--- lib/pure/osproc.nim | 21 +++---- testament/lib/stdtest/specialpaths.nim | 31 ++++++++++ tests/nim.cfg | 1 + tests/stdlib/t10231.nim | 2 + tests/stdlib/tos.nim | 2 + tests/stdlib/tosproc.nim | 109 +++++++++++++++++++++++++++------ 8 files changed, 148 insertions(+), 41 deletions(-) create mode 100644 testament/lib/stdtest/specialpaths.nim create mode 100644 tests/nim.cfg diff --git a/.gitignore b/.gitignore index 132cd7f60..2bf0bf30c 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,7 @@ doc/*.html doc/*.pdf doc/*.idx /web/upload -build/* +/build/* bin/* # iOS specific wildcards. diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 3a959d4e2..32fa2885e 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1287,6 +1287,17 @@ proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", discard tryRemoveFile(dest) raise +proc exitStatusLikeShell*(status: cint): cint = + ## converts exit code from `c_system` into a shell exit code + when defined(posix) and not weirdTarget: + if WIFSIGNALED(status): + # like the shell! + 128 + WTERMSIG(status) + else: + WEXITSTATUS(status) + else: + status + proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", tags: [ExecIOEffect], noNimScript.} = ## Executes a `shell command`:idx:. @@ -1295,14 +1306,8 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", ## line arguments given to program. The proc returns the error code ## of the shell when it has finished. The proc does not return until ## the process has finished. To execute a program without having a - ## shell involved, use the `execProcess` proc of the `osproc` - ## module. - when defined(macosx): - result = c_system(command) shr 8 - elif defined(posix): - result = WEXITSTATUS(c_system(command)) - else: - result = c_system(command) + ## shell involved, use `osproc.execProcess`. + result = exitStatusLikeShell(c_system(command)) # Templates for filtering directories and files when defined(windows) and not weirdTarget: diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 72581f47c..dfe75d998 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -724,13 +724,6 @@ elif not defined(useNimRtl): proc isExitStatus(status: cint): bool = WIFEXITED(status) or WIFSIGNALED(status) - proc exitStatus(status: cint): cint = - if WIFSIGNALED(status): - # like the shell! - 128 + WTERMSIG(status) - else: - WEXITSTATUS(status) - proc envToCStringArray(t: StringTableRef): cstringArray = result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring))) var i = 0 @@ -1054,7 +1047,7 @@ elif not defined(useNimRtl): proc waitForExit(p: Process, timeout: int = -1): int = if p.exitFlag: - return exitStatus(p.exitStatus) + return exitStatusLikeShell(p.exitStatus) if timeout == -1: var status: cint = 1 @@ -1109,7 +1102,7 @@ elif not defined(useNimRtl): finally: discard posix.close(kqFD) - result = exitStatus(p.exitStatus) + result = exitStatusLikeShell(p.exitStatus) else: import times @@ -1142,7 +1135,7 @@ elif not defined(useNimRtl): s.tv_nsec = b.tv_nsec if p.exitFlag: - return exitStatus(p.exitStatus) + return exitStatusLikeShell(p.exitStatus) if timeout == -1: var status: cint = 1 @@ -1220,20 +1213,20 @@ elif not defined(useNimRtl): if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1: raiseOSError(osLastError()) - result = exitStatus(p.exitStatus) + result = exitStatusLikeShell(p.exitStatus) proc peekExitCode(p: Process): int = var status = cint(0) result = -1 if p.exitFlag: - return exitStatus(p.exitStatus) + return exitStatusLikeShell(p.exitStatus) var ret = waitpid(p.id, status, WNOHANG) if ret > 0: if isExitStatus(status): p.exitFlag = true p.exitStatus = status - result = exitStatus(status) + result = exitStatusLikeShell(status) proc createStream(stream: var Stream, handle: var FileHandle, fileMode: FileMode) = @@ -1265,7 +1258,7 @@ elif not defined(useNimRtl): proc execCmd(command: string): int = when defined(linux): let tmp = csystem(command) - result = if tmp == -1: tmp else: exitStatus(tmp) + result = if tmp == -1: tmp else: exitStatusLikeShell(tmp) else: result = csystem(command) diff --git a/testament/lib/stdtest/specialpaths.nim b/testament/lib/stdtest/specialpaths.nim new file mode 100644 index 000000000..3c8b2338c --- /dev/null +++ b/testament/lib/stdtest/specialpaths.nim @@ -0,0 +1,31 @@ +#[ +todo: move findNimStdLibCompileTime, findNimStdLib here +]# + +import os + +# Note: all the const paths defined here are known at compile time and valid +# so long Nim repo isn't relocated after compilation. +# This means the binaries they produce will embed hardcoded paths, which +# isn't appropriate for some applications that need to be relocatable. + +const sourcePath = currentSourcePath() + # robust way to derive other paths here + # We don't depend on PATH so this is robust to having multiple nim + # binaries + +const nimRootDir* = sourcePath.parentDir.parentDir.parentDir.parentDir + ## root of Nim repo + +const stdlibDir* = nimRootDir / "lib" + # todo: make nimeval.findNimStdLibCompileTime use this + +const systemPath* = stdlibDir / "system.nim" + +const buildDir* = nimRootDir / "build" + ## refs #10268: all testament generated files should go here to avoid + ## polluting .gitignore + +static: + # sanity check + doAssert fileExists(systemPath) diff --git a/tests/nim.cfg b/tests/nim.cfg new file mode 100644 index 000000000..577baaacd --- /dev/null +++ b/tests/nim.cfg @@ -0,0 +1 @@ +--path:"../testament/lib" # so we can `import stdtest/foo` in this dir diff --git a/tests/stdlib/t10231.nim b/tests/stdlib/t10231.nim index 5d1101aa4..2bb64b475 100644 --- a/tests/stdlib/t10231.nim +++ b/tests/stdlib/t10231.nim @@ -6,6 +6,8 @@ discard """ import os +# consider moving this inside tosproc (taking care that it's for cpp mode) + if paramCount() == 0: # main process doAssert execShellCmd(getAppFilename().quoteShell & " test") == 1 diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim index a7cf5d5b6..e4e14d5a1 100644 --- a/tests/stdlib/tos.nim +++ b/tests/stdlib/tos.nim @@ -263,3 +263,5 @@ block splitFile: doAssert splitFile("abc/.") == ("abc", ".", "") doAssert splitFile("..") == ("", "..", "") doAssert splitFile("a/..") == ("a", "..", "") + +# execShellCmd is tested in tosproc diff --git a/tests/stdlib/tosproc.nim b/tests/stdlib/tosproc.nim index 9d57d4574..7a7c1836f 100644 --- a/tests/stdlib/tosproc.nim +++ b/tests/stdlib/tosproc.nim @@ -1,23 +1,96 @@ -discard """ - output: "" -""" # test the osproc module -import os, osproc +import stdtest/specialpaths +import "../.." / compiler/unittest_light -block execProcessTest: - let dir = parentDir(currentSourcePath()) - let (outp, err) = execCmdEx("nim c " & quoteShell(dir / "osproctest.nim")) - doAssert err == 0 - let exePath = dir / addFileExt("osproctest", ExeExt) - let outStr1 = execProcess(exePath, workingDir=dir, args=["foo", "b A r"], options={}) - doAssert outStr1 == dir & "\nfoo\nb A r\n" +when defined(case_testfile): # compiled test file for child process + from posix import exitnow + proc c_exit2(code: c_int): void {.importc: "_exit", header: "".} + import os + var a = 0 + proc fun(b = 0) = + a.inc + if a mod 10000000 == 0: # prevents optimizing it away + echo a + fun(b+1) - const testDir = "t e st" - createDir(testDir) - doAssert dirExists(testDir) - let outStr2 = execProcess(exePath, workingDir=testDir, args=["x yz"], options={}) - doAssert outStr2 == absolutePath(testDir) & "\nx yz\n" + proc main() = + let args = commandLineParams() + echo (msg: "child binary", pid: getCurrentProcessId()) + let arg = args[0] + echo (arg: arg) + case arg + of "exit_0": + if true: quit(0) + of "exitnow_139": + if true: exitnow(139) + of "c_exit2_139": + if true: c_exit2(139) + of "quit_139": + # `exitStatusLikeShell` doesn't distinguish between a process that + # exit(139) and a process that gets killed with `SIGSEGV` because + # 139 = 11 + 128 = SIGSEGV + 128. + # However, as #10249 shows, this leads to bad debugging experience + # when a child process dies with SIGSEGV, leaving no trace of why it + # failed. The shell (and lldb debugger) solves that by inserting a + # helpful msg: `segmentation fault` when it detects a signal killed + # the child. + # todo: expose an API that will show more diagnostic, returing + # (exitCode, signal) instead of just `shellExitCode`. + if true: quit(139) + of "exit_recursion": # stack overflow by infinite recursion + fun() + echo a + of "exit_array": # bad array access + echo args[1] + main() - removeDir(testDir) - removeFile(exePath) +else: + + import os, osproc, strutils, posix + + block execShellCmdTest: + ## first, compile child program + const nim = getCurrentCompilerExe() + const sourcePath = currentSourcePath() + let output = buildDir / "D20190111T024543".addFileExt(ExeExt) + let cmd = "$# c -o:$# -d:release -d:case_testfile $#" % [nim, output, + sourcePath] + # we're testing `execShellCmd` so don't rely on it to compile test file + # note: this should be exported in posix.nim + proc c_system(cmd: cstring): cint {.importc: "system", + header: "".} + assertEquals c_system(cmd), 0 + + ## use it + template runTest(arg: string, expected: int) = + echo (arg2: arg, expected2: expected) + assertEquals execShellCmd(output & " " & arg), expected + + runTest("exit_0", 0) + runTest("exitnow_139", 139) + runTest("c_exit2_139", 139) + runTest("quit_139", 139) + runTest("exit_array", 1) + when defined(posix): # on windows, -1073741571 + runTest("exit_recursion", SIGSEGV.int + 128) # bug #10273: was returning 0 + assertEquals exitStatusLikeShell(SIGSEGV), SIGSEGV + 128.cint + + block execProcessTest: + let dir = parentDir(currentSourcePath()) + let (outp, err) = execCmdEx("nim c " & quoteShell(dir / "osproctest.nim")) + doAssert err == 0 + let exePath = dir / addFileExt("osproctest", ExeExt) + let outStr1 = execProcess(exePath, workingDir = dir, args = ["foo", + "b A r"], options = {}) + doAssert outStr1 == dir & "\nfoo\nb A r\n" + + const testDir = "t e st" + createDir(testDir) + doAssert dirExists(testDir) + let outStr2 = execProcess(exePath, workingDir = testDir, args = ["x yz"], + options = {}) + doAssert outStr2 == absolutePath(testDir) & "\nx yz\n" + + removeDir(testDir) + removeFile(exePath) -- cgit 1.4.1-2-gfad0 From 2823da5e528b55cb5178e1bc72183bf4638fbf28 Mon Sep 17 00:00:00 2001 From: Strømberg Date: Sun, 13 Jan 2019 09:04:40 +0100 Subject: setCommand nop update documentation (#10254) When I used Warning: Using `setCommand 'nop'` is not necessary. --- doc/nims.rst | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/doc/nims.rst b/doc/nims.rst index 6cc894b91..eaf20a6db 100644 --- a/doc/nims.rst +++ b/doc/nims.rst @@ -117,17 +117,6 @@ Task Description ========= =================================================== -If the task runs an external command via ``exec`` it should afterwards call -``setCommand "nop"`` to tell the Nim compiler that nothing else needs to be -done: - -.. code-block:: nim - - task tests, "test regular expressions": - exec "nim c -r tests" - setCommand "nop" - - Look at the module `distros `_ for some support of the OS's native package managers. -- cgit 1.4.1-2-gfad0 From 5cd18b0f240ff111e8892aef7bbab5037c3888bf Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 13 Jan 2019 09:04:13 +0100 Subject: make system.task compatible with NimScript; refs #10254 --- lib/system/nimscript.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim index 34daf30a9..ae97e88fa 100644 --- a/lib/system/nimscript.nim +++ b/lib/system/nimscript.nim @@ -341,14 +341,15 @@ when not defined(nimble): ## .. code-block:: nim ## task build, "default build is via the C backend": ## setCommand "c" - proc `name Task`*() = body + proc `name Task`*() = + setCommand "nop" + body let cmd = getCommand() if cmd.len == 0 or cmd ==? "help": setCommand "help" writeTask(astToStr(name), description) elif cmd ==? astToStr(name): - setCommand "nop" `name Task`() # nimble has its own implementation for these things. -- cgit 1.4.1-2-gfad0 From 40115cd6458457d071600526aa1037eaffe99f79 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 13 Jan 2019 13:30:01 +0100 Subject: improve error messages quality for '.push: raises []' --- compiler/pragmas.nim | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 4adf6032b..3967fa22d 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -1123,7 +1123,14 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, else: invalidPragma(c, it) +proc overwriteLineInfo(n: PNode; info: TLineInfo) = + n.info = info + for i in 0.. Date: Sun, 13 Jan 2019 13:54:44 +0100 Subject: make tests more robust; tests should be deterministic, no randomize() calls in tests --- tests/stdlib/tmath.nim | 24 ++++++++++++------------ tests/stdlib/tosproc.nim | 5 ++++- tests/stdlib/tunittest.nim | 1 - 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/stdlib/tmath.nim b/tests/stdlib/tmath.nim index 544fa8dc0..bdb5aa332 100644 --- a/tests/stdlib/tmath.nim +++ b/tests/stdlib/tmath.nim @@ -20,29 +20,29 @@ import sets, tables suite "random int": test "there might be some randomness": var set = initSet[int](128) - randomize() + for i in 1..1000: incl(set, random(high(int))) check len(set) == 1000 test "single number bounds work": - randomize() + var rand: int for i in 1..1000: rand = random(1000) check rand < 1000 check rand > -1 test "slice bounds work": - randomize() + var rand: int for i in 1..1000: rand = random(100..1000) check rand < 1000 check rand >= 100 - test "randomize() again gives new numbers": - randomize() + test " again gives new numbers": + var rand1 = random(1000000) os.sleep(200) - randomize() + var rand2 = random(1000000) check rand1 != rand2 @@ -50,29 +50,29 @@ suite "random int": suite "random float": test "there might be some randomness": var set = initSet[float](128) - randomize() + for i in 1..100: incl(set, random(1.0)) check len(set) == 100 test "single number bounds work": - randomize() + var rand: float for i in 1..1000: rand = random(1000.0) check rand < 1000.0 check rand > -1.0 test "slice bounds work": - randomize() + var rand: float for i in 1..1000: rand = random(100.0..1000.0) check rand < 1000.0 check rand >= 100.0 - test "randomize() again gives new numbers": - randomize() + test " again gives new numbers": + var rand1:float = random(1000000.0) os.sleep(200) - randomize() + var rand2:float = random(1000000.0) check rand1 != rand2 diff --git a/tests/stdlib/tosproc.nim b/tests/stdlib/tosproc.nim index 7a7c1836f..b8d3be9bb 100644 --- a/tests/stdlib/tosproc.nim +++ b/tests/stdlib/tosproc.nim @@ -93,4 +93,7 @@ else: doAssert outStr2 == absolutePath(testDir) & "\nx yz\n" removeDir(testDir) - removeFile(exePath) + try: + removeFile(exePath) + except OSError: + discard diff --git a/tests/stdlib/tunittest.nim b/tests/stdlib/tunittest.nim index c8656bbff..65baefef0 100644 --- a/tests/stdlib/tunittest.nim +++ b/tests/stdlib/tunittest.nim @@ -50,7 +50,6 @@ test "unittest multiple requires": import math, random from strutils import parseInt proc defectiveRobot() = - randomize() case random(1..4) of 1: raise newException(OSError, "CANNOT COMPUTE!") of 2: discard parseInt("Hello World!") -- cgit 1.4.1-2-gfad0 From d740a5b79a004632f90da6702cd351ae214e273d Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 13 Jan 2019 13:06:16 +0000 Subject: Implements BackwardsIndex `[]` for deque. (#10105) --- lib/pure/collections/deques.nim | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/pure/collections/deques.nim b/lib/pure/collections/deques.nim index e8342e208..5243b96a6 100644 --- a/lib/pure/collections/deques.nim +++ b/lib/pure/collections/deques.nim @@ -88,11 +88,23 @@ proc `[]`*[T](deq: var Deque[T], i: Natural): var T {.inline.} = xBoundsCheck(deq, i) return deq.data[(deq.head + i) and deq.mask] -proc `[]=`* [T] (deq: var Deque[T], i: Natural, val : T) {.inline.} = +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 +proc `[]`*[T](deq: var Deque[T], i: BackwardsIndex): var T {.inline.} = + ## Access the backwards indexed i-th element. + return deq[deq.len - int(i)] + +proc `[]`*[T](deq: Deque[T], i: BackwardsIndex): T {.inline.} = + ## Access the backwards indexed i-th element. + return deq[deq.len - int(i)] + +proc `[]=`*[T](deq: var Deque[T], i: BackwardsIndex, x: T) {.inline.} = + ## Change the backwards indexed i-th element. + deq[deq.len - int(i)] = x + iterator items*[T](deq: Deque[T]): T = ## Yield every element of `deq`. var i = deq.head -- cgit 1.4.1-2-gfad0 From 0c10fc67eb259ede0de4501cee5fe81463064294 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 13 Jan 2019 14:05:09 +0100 Subject: fixes #10104 --- compiler/vm.nim | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/compiler/vm.nim b/compiler/vm.nim index e2586e615..c001981f8 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -85,7 +85,7 @@ proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int; recursionLimit=100) = add(s, x.prc.name.s) msgWriteln(c.config, s) -proc stackTrace(c: PCtx, tos: PStackFrame, pc: int, +proc stackTraceImpl(c: PCtx, tos: PStackFrame, pc: int, msg: string, lineInfo: TLineInfo) = msgWriteln(c.config, "stack trace: (most recent call last)") stackTraceAux(c, tos, pc) @@ -93,8 +93,14 @@ proc stackTrace(c: PCtx, tos: PStackFrame, pc: int, if c.mode == emRepl: globalError(c.config, lineInfo, msg) else: localError(c.config, lineInfo, msg) -proc stackTrace(c: PCtx, tos: PStackFrame, pc: int, msg: string) = - stackTrace(c, tos, pc, msg, c.debug[pc]) +template stackTrace(c: PCtx, tos: PStackFrame, pc: int, + msg: string, lineInfo: TLineInfo) = + stackTraceImpl(c, tos, pc, msg, lineInfo) + return + +template stackTrace(c: PCtx, tos: PStackFrame, pc: int, msg: string) = + stackTraceImpl(c, tos, pc, msg, c.debug[pc]) + return proc bailOut(c: PCtx; tos: PStackFrame) = stackTrace(c, tos, c.exceptionInstr, "unhandled exception: " & @@ -955,13 +961,13 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = decodeBC(rkInt) let a = regs[rb].node let b = regs[rc].node - if a.kind == nkSym and a.sym.kind in skProcKinds and + if a.kind == nkSym and a.sym.kind in skProcKinds and b.kind == nkSym and b.sym.kind in skProcKinds: regs[ra].intVal = if sfFromGeneric in a.sym.flags and a.sym.owner == b.sym: 1 else: 0 - else: - stackTrace(c, tos, pc, "node is not a proc symbol") + else: + stackTrace(c, tos, pc, "node is not a proc symbol") of opcEcho: let rb = instr.regB if rb == 1: -- cgit 1.4.1-2-gfad0 From 7f0559b93fbd21283119f545ac8bf805d1a6eeb4 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 13 Jan 2019 14:08:49 +0100 Subject: fixes #7241 --- lib/impure/db_sqlite.nim | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/impure/db_sqlite.nim b/lib/impure/db_sqlite.nim index c7e373098..3910992bb 100644 --- a/lib/impure/db_sqlite.nim +++ b/lib/impure/db_sqlite.nim @@ -166,10 +166,12 @@ iterator fastRows*(db: DbConn, query: SqlQuery, var stmt = setupQuery(db, query, args) var L = (column_count(stmt)) var result = newRow(L) - while step(stmt) == SQLITE_ROW: - setRow(stmt, result, L) - yield result - if finalize(stmt) != SQLITE_OK: dbError(db) + try: + while step(stmt) == SQLITE_ROW: + setRow(stmt, result, L) + yield result + finally: + if finalize(stmt) != SQLITE_OK: dbError(db) iterator instantRows*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): InstantRow @@ -177,9 +179,11 @@ iterator instantRows*(db: DbConn, query: SqlQuery, ## same as fastRows but returns a handle that can be used to get column text ## on demand using []. Returned handle is valid only within the iterator body. var stmt = setupQuery(db, query, args) - while step(stmt) == SQLITE_ROW: - yield stmt - if finalize(stmt) != SQLITE_OK: dbError(db) + try: + while step(stmt) == SQLITE_ROW: + yield stmt + finally: + if finalize(stmt) != SQLITE_OK: dbError(db) proc toTypeKind(t: var DbType; x: int32) = case x @@ -210,9 +214,11 @@ iterator instantRows*(db: DbConn; columns: var DbColumns; query: SqlQuery, ## on demand using []. Returned handle is valid only within the iterator body. var stmt = setupQuery(db, query, args) setColumns(columns, stmt) - while step(stmt) == SQLITE_ROW: - yield stmt - if finalize(stmt) != SQLITE_OK: dbError(db) + try: + while step(stmt) == SQLITE_ROW: + yield stmt + finally: + if finalize(stmt) != SQLITE_OK: dbError(db) proc `[]`*(row: InstantRow, col: int32): string {.inline.} = ## returns text for given column of the row -- cgit 1.4.1-2-gfad0 From 451377f737ab4b21a96c1ca3f3646c86c275a2b3 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 13 Jan 2019 14:24:38 +0100 Subject: fixes #10271 [backport] --- lib/system/gc_ms.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/system/gc_ms.nim b/lib/system/gc_ms.nim index d8cb3e2d0..aa5fb6aea 100644 --- a/lib/system/gc_ms.nim +++ b/lib/system/gc_ms.nim @@ -485,8 +485,9 @@ proc collectCTBody(gch: var GcHeap) = sysAssert(allocInv(gch.region), "collectCT: end") proc collectCT(gch: var GcHeap; size: int) = + let fmem = getFreeMem(gch.region) if (getOccupiedMem(gch.region) >= gch.cycleThreshold or - size > getFreeMem(gch.region)) and gch.recGcLock == 0: + size > fmem and fmem > InitialThreshold) and gch.recGcLock == 0: collectCTBody(gch) when not defined(useNimRtl): -- cgit 1.4.1-2-gfad0 From ab99bdfc408e56a2e8a1a3ada21effe983433851 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 13 Jan 2019 14:56:50 +0100 Subject: fixes #10136 --- compiler/semexprs.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 8852cb653..657df36dd 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -403,8 +403,8 @@ proc semIs(c: PContext, n: PNode, flags: TExprFlags): PNode = n[1] = makeTypeSymNode(c, lhsType, n[1].info) lhsType = n[1].typ else: - internalAssert c.config, lhsType.base.kind != tyNone - if c.inGenericContext > 0 and lhsType.base.containsGenericType: + if lhsType.base.kind == tyNone or + (c.inGenericContext > 0 and lhsType.base.containsGenericType): # BUGFIX: don't evaluate this too early: ``T is void`` return -- cgit 1.4.1-2-gfad0 From aa7ad8897895acaee9e2999652050d5bc52921a7 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 13 Jan 2019 15:52:50 +0100 Subject: fixes #10075 [backport] --- compiler/sigmatch.nim | 3 +-- tests/macros/tvarargsuntyped.nim | 31 ++++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 0915f303b..9aae254f3 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -2267,8 +2267,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, if n.sons[a].kind == nkHiddenStdConv: doAssert n.sons[a].sons[0].kind == nkEmpty and - n.sons[a].sons[1].kind == nkArgList and - n.sons[a].sons[1].len == 0 + n.sons[a].sons[1].kind in {nkBracket, nkArgList} # Steal the container and pass it along setSon(m.call, formal.position + 1, n.sons[a].sons[1]) else: diff --git a/tests/macros/tvarargsuntyped.nim b/tests/macros/tvarargsuntyped.nim index 657ed47d6..f0fcff662 100644 --- a/tests/macros/tvarargsuntyped.nim +++ b/tests/macros/tvarargsuntyped.nim @@ -3,7 +3,9 @@ discard """ (left: 2, r: 7, x: 8, height: 4, s: test, width: 3, y: 9, top: 1, g: 7, b: 8) (left: 2, r: 7, x: 8, height: 4, s: text, width: 3, y: 9, top: 1, g: 7, b: 8) (left: 2, r: 7, x: 8, height: 4, s: text, width: 3, y: 9, top: 4, g: 7, b: 8) -(left: 2, r: 7, x: 8, height: 4, s: test, width: 3, y: 9, top: 1, g: 7, b: 8)''' +(left: 2, r: 7, x: 8, height: 4, s: test, width: 3, y: 9, top: 1, g: 7, b: 8) +10 +hello 18.0''' """ import macros @@ -78,3 +80,30 @@ let width: cint = 3 let height: cint = 4 bar(rect(top, left, width, height), "test", point(8, 9), color(7,7,8)) + + +# bug #10075 + +import macros + +proc convert_hidden_stdconv(args: NimNode): NimNode = + var n = args + while n.len == 1 and n[0].kind == nnkHiddenStdConv: + n = n[0][1] + return n + +macro t2(s: int, v: varargs[untyped]): untyped = + let v = convert_hidden_stdconv(v) + echo v.treeRepr + let (v1, v2) = (v[0], v[1]) + quote do: + echo `v1`, " ", `v2` + +template t1(s: int, v: varargs[typed]) = + #static: + # dumpTree v + echo s + t2(s, v) + +when isMainModule: + t1(10, "hello", 18.0) -- cgit 1.4.1-2-gfad0 From 2ccc9db59d849f3dc5613236eea4a47c9ab19be1 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 13 Jan 2019 16:05:42 +0100 Subject: closes #3744 --- tests/macros/tquotedo.nim | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/macros/tquotedo.nim b/tests/macros/tquotedo.nim index cd1f69116..0aae87bf0 100644 --- a/tests/macros/tquotedo.nim +++ b/tests/macros/tquotedo.nim @@ -3,6 +3,7 @@ output: ''' 123 Hallo Welt Hallo Welt +1 ''' """ @@ -23,3 +24,13 @@ macro foobar(arg: untyped): untyped = foobar: echo "Hallo Welt" + +# bug #3744 +import macros +macro t(): untyped = + return quote do: + proc tp(): int = + result = 1 +t() + +echo tp() -- cgit 1.4.1-2-gfad0 From bd5dd2b95bb17a07d1400d677e80c0514fef69d0 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 13 Jan 2019 16:22:19 +0100 Subject: nimgrep: fixes #989 --- tools/nimgrep.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nimgrep.nim b/tools/nimgrep.nim index 8c6353e31..b26d862b6 100644 --- a/tools/nimgrep.nim +++ b/tools/nimgrep.nim @@ -160,7 +160,7 @@ proc processFile(pattern; filename: string; counter: var int) = var reallyReplace = true while i < buffer.len: let t = findBounds(buffer, pattern, matches, i) - if t.first < 0: break + if t.first < 0 or t.last < t.first: break inc(line, countLines(buffer, i, t.first-1)) var wholeMatch = buffer.substr(t.first, t.last) -- cgit 1.4.1-2-gfad0 From 80c69ffd31ec896497479203d3c282a5ba08d9c4 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 13 Jan 2019 16:23:08 +0100 Subject: nimgrep: use standard regular expression syntax, not reExtended --- tools/nimgrep.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nimgrep.nim b/tools/nimgrep.nim index b26d862b6..0a835b038 100644 --- a/tools/nimgrep.nim +++ b/tools/nimgrep.nim @@ -336,7 +336,7 @@ else: for f in items(filenames): walker(pegp, f, counter) else: - var reflags = {reStudy, reExtended} + var reflags = {reStudy} if optIgnoreStyle in options: pattern = styleInsensitive(pattern) if optWord in options: -- cgit 1.4.1-2-gfad0 From 3c2d82eaae8054e1f52201d1429214dd15cfef95 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 13 Jan 2019 17:19:36 +0100 Subject: make megatest green --- tests/macros/tvarargsuntyped.nim | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/macros/tvarargsuntyped.nim b/tests/macros/tvarargsuntyped.nim index f0fcff662..5a06adcca 100644 --- a/tests/macros/tvarargsuntyped.nim +++ b/tests/macros/tvarargsuntyped.nim @@ -105,5 +105,4 @@ template t1(s: int, v: varargs[typed]) = echo s t2(s, v) -when isMainModule: - t1(10, "hello", 18.0) +t1(10, "hello", 18.0) -- cgit 1.4.1-2-gfad0 From d983d998b25dc1eda004b2483d620315e9ea64b2 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 13 Jan 2019 18:53:27 +0100 Subject: fixes #9218 --- lib/nimbase.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/nimbase.h b/lib/nimbase.h index 2cb632787..ba4273726 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -166,13 +166,13 @@ __clang__ # define N_STDCALL(rettype, name) rettype __stdcall name # define N_SYSCALL(rettype, name) rettype __syscall name # define N_FASTCALL(rettype, name) rettype __fastcall name -# define N_SAFECALL(rettype, name) rettype __safecall name +# define N_SAFECALL(rettype, name) rettype __stdcall name /* function pointers with calling convention: */ # define N_CDECL_PTR(rettype, name) rettype (__cdecl *name) # define N_STDCALL_PTR(rettype, name) rettype (__stdcall *name) # define N_SYSCALL_PTR(rettype, name) rettype (__syscall *name) # define N_FASTCALL_PTR(rettype, name) rettype (__fastcall *name) -# define N_SAFECALL_PTR(rettype, name) rettype (__safecall *name) +# define N_SAFECALL_PTR(rettype, name) rettype (__stdcall *name) # ifdef __cplusplus # define N_LIB_EXPORT extern "C" __declspec(dllexport) -- cgit 1.4.1-2-gfad0 From 2b37bf5d5bf9e9ee7cd387fa455df8931f8e6666 Mon Sep 17 00:00:00 2001 From: Arne Döring Date: Mon, 14 Jan 2019 09:20:31 +0100 Subject: added proc rand for typedesc (#10259) --- lib/pure/random.nim | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pure/random.nim b/lib/pure/random.nim index ee728ad4a..378ca6f87 100644 --- a/lib/pure/random.nim +++ b/lib/pure/random.nim @@ -158,10 +158,14 @@ proc rand*[T](x: HSlice[T, T]): T = result = rand(state, x) proc rand*[T](r: var Rand; a: openArray[T]): T {.deprecated.} = - ## returns a random element from the openarray `a`. + ## Returns a random element from the openarray `a`. ## **Deprecated since v0.20.0:** use ``sample`` instead. result = a[rand(r, a.low..a.high)] +proc rand*[T: SomeInteger](t: typedesc[T]): T = + ## Returns a random integer in the range `low(T)..high(T)`. + result = cast[T](state.next) + proc rand*[T](a: openArray[T]): T {.deprecated.} = ## returns a random element from the openarray `a`. ## **Deprecated since v0.20.0:** use ``sample`` instead. -- cgit 1.4.1-2-gfad0 From 29e0a792ffff7a08636f0bb50709a4d491cdf19b Mon Sep 17 00:00:00 2001 From: narimiran Date: Mon, 14 Jan 2019 09:24:12 +0100 Subject: [backport] fix documentation leak to `os.nim`[ci skip] --- lib/pure/includes/osenv.nim | 2 +- lib/pure/includes/oserr.nim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pure/includes/osenv.nim b/lib/pure/includes/osenv.nim index 4acc36b93..945555540 100644 --- a/lib/pure/includes/osenv.nim +++ b/lib/pure/includes/osenv.nim @@ -1,4 +1,4 @@ -## Include file that implements 'getEnv' and friends. Do not import it! +# Include file that implements 'getEnv' and friends. Do not import it! when not declared(os): {.error: "This is an include file for os.nim!".} diff --git a/lib/pure/includes/oserr.nim b/lib/pure/includes/oserr.nim index abd0bf501..9ae06f5bc 100644 --- a/lib/pure/includes/oserr.nim +++ b/lib/pure/includes/oserr.nim @@ -1,4 +1,4 @@ -## Include file that implements 'osErrorMsg' and friends. Do not import it! +# Include file that implements 'osErrorMsg' and friends. Do not import it! when not declared(os): {.error: "This is an include file for os.nim!".} -- cgit 1.4.1-2-gfad0 From c707267212a8e5cd5056296acf6502d58c9e5de9 Mon Sep 17 00:00:00 2001 From: genotrance Date: Mon, 14 Jan 2019 02:26:10 -0600 Subject: Add stdin read support to nimscrit #3983 (#10292) --- compiler/scriptconfig.nim | 15 ++++++++++++--- lib/system/nimscript.nim | 29 ++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim index e3a9478d1..db60dafcc 100644 --- a/compiler/scriptconfig.nim +++ b/compiler/scriptconfig.nim @@ -42,15 +42,18 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; proc (a: VmArgs) = body - template cbos(name, body) {.dirty.} = + template cbexc(name, exc, body) {.dirty.} = result.registerCallback "stdlib.system." & astToStr(name), proc (a: VmArgs) = errorMsg = "" try: body - except OSError: + except exc: errorMsg = getCurrentExceptionMsg() + template cbos(name, body) {.dirty.} = + cbexc(name, OSError, body) + # Idea: Treat link to file as a file, but ignore link to directory to prevent # endless recursions out of the box. cbos listFiles: @@ -64,7 +67,7 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; cbos createDir: os.createDir getString(a, 0) - result.registerCallback "stdlib.system.getOsError", + result.registerCallback "stdlib.system.getError", proc (a: VmArgs) = setResult(a, errorMsg) cbos setCurrentDir: @@ -157,6 +160,12 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; setResult(a, os.getAppFilename()) cbconf cppDefine: options.cppDefine(conf, a.getString(0)) + cbexc stdinReadLine, EOFError: + setResult(a, "") + setResult(a, stdin.readLine()) + cbexc stdinReadAll, EOFError: + setResult(a, "") + setResult(a, stdin.readAll()) proc runNimScript*(cache: IdentCache; scriptName: AbsoluteFile; freshDefines=true; conf: ConfigRef) = diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim index ae97e88fa..e879fda83 100644 --- a/lib/system/nimscript.nim +++ b/lib/system/nimscript.nim @@ -46,7 +46,7 @@ proc copyDir(src, dest: string) {. tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin proc createDir(dir: string) {.tags: [WriteIOEffect], raises: [OSError].} = builtin -proc getOsError: string = builtin +proc getError: string = builtin proc setCurrentDir(dir: string) = builtin proc getCurrentDir*(): string = ## Retrieves the current working directory. @@ -178,9 +178,12 @@ var mode*: ScriptMode ## Set this to influence how mkDir, rmDir, rmFile etc. ## behave +template checkError(exc: untyped): untyped = + let err = getError() + if err.len > 0: raise newException(exc, err) + template checkOsError = - let err = getOsError() - if err.len > 0: raise newException(OSError, err) + checkError(OSError) template log(msg: string, body: untyped) = if mode in {ScriptMode.Verbose, ScriptMode.Whatif}: @@ -332,6 +335,26 @@ proc cppDefine*(define: string) = ## needs to be mangled. builtin +proc stdinReadLine(): TaintedString {. + tags: [ReadIOEffect], raises: [IOError].} = + builtin + +proc stdinReadAll(): TaintedString {. + tags: [ReadIOEffect], raises: [IOError].} = + builtin + +proc readLineFromStdin*(): TaintedString {.raises: [IOError].} = + ## Reads a line of data from stdin - blocks until \n or EOF which happens when stdin is closed + log "readLineFromStdin": + result = stdinReadLine() + checkError(EOFError) + +proc readAllFromStdin*(): TaintedString {.raises: [IOError].} = + ## Reads all data from stdin - blocks until EOF which happens when stdin is closed + log "readAllFromStdin": + result = stdinReadAll() + checkError(EOFError) + when not defined(nimble): template `==?`(a, b: string): bool = cmpIgnoreStyle(a, b) == 0 template task*(name: untyped; description: string; body: untyped): untyped = -- cgit 1.4.1-2-gfad0 From 65593e76f44176e3a28d4d60aff79e58042244ad Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Mon, 14 Jan 2019 02:58:16 -0800 Subject: fixes leftover from #9647 : use nimf as standardized extension for source code filters (#10294) --- testament/htmlgen.nim | 2 +- testament/testamenthtml.nimf | 297 ++++++++++++++++++++++++++++++++++++++++++ testament/testamenthtml.templ | 297 ------------------------------------------ 3 files changed, 298 insertions(+), 298 deletions(-) create mode 100644 testament/testamenthtml.nimf delete mode 100644 testament/testamenthtml.templ diff --git a/testament/htmlgen.nim b/testament/htmlgen.nim index 34902f71e..641c1c32c 100644 --- a/testament/htmlgen.nim +++ b/testament/htmlgen.nim @@ -11,7 +11,7 @@ import cgi, backend, strutils, json, os, tables, times -import "testamenthtml.templ" +import "testamenthtml.nimf" proc generateTestResultPanelPartial(outfile: File, testResultRow: JsonNode) = let diff --git a/testament/testamenthtml.nimf b/testament/testamenthtml.nimf new file mode 100644 index 000000000..9190f370e --- /dev/null +++ b/testament/testamenthtml.nimf @@ -0,0 +1,297 @@ +#? stdtmpl(subsChar = '%', metaChar = '#', emit = "outfile.write") +#import strutils +# +#proc htmlQuote*(raw: string): string = +# result = raw.multiReplace( +# ("&", "&"), +# ("\"", """), +# ("'", "'"), +# ("<", "<"), +# (">", ">") +# ) +# +#end proc +#proc generateHtmlBegin*(outfile: File) = + + + + + Testament Test Results + + + + + + + +
        +

        Testament Test Results Nim Tester

        +#end proc +#proc generateHtmlAllTestsBegin*(outfile: File, machine, commit, branch: string, +# totalCount: BiggestInt, +# successCount: BiggestInt, successPercentage: string, +# ignoredCount: BiggestInt, ignoredPercentage: string, +# failedCount: BiggestInt, failedPercentage: string, onlyFailing = false) = +
        +
        Hostname
        +
        %machine
        +
        Git Commit
        +
        %commit
        +
        Branch ref.
        +
        %branch
        +
        +
        +
        All Tests
        +
        + + %totalCount +
        +
        Successful Tests
        +
        + + %successCount (%successPercentage) +
        +
        Skipped Tests
        +
        + + %ignoredCount (%ignoredPercentage) +
        +
        Failed Tests
        +
        + + %failedCount (%failedPercentage) +
        +
        +
        + +# if not onlyFailing: + + + + + + + + +# end if + + + + + + + + +
        All Tests +
        + + + + +
        +
        Successful Tests +
        + + + + +
        +
        Skipped Tests +
        + + + + +
        +
        Failed Tests +
        + + + + +
        +
        +
        +
        +#end proc +#proc generateHtmlTestresultPanelBegin*(outfile: File, trId, name, target, category, +# action, resultDescription, timestamp, result, resultSign, +# panelCtxClass, textCtxClass, bgCtxClass: string) = +
        + +
        +
        +
        Name
        +
        %name
        +
        Category
        +
        %category
        +
        Timestamp
        +
        %timestamp
        +
        Nim Action
        +
        %action
        +
        Nim Backend Target
        +
        %target
        +
        Code
        +
        %result
        +
        +#end proc +#proc generateHtmlTestresultOutputDetails*(outfile: File, expected, gotten: string) = +
        + + + + + + + + + + + + + +
        ExpectedActual
        %expected
        %gotten
        +
        +#end proc +#proc generateHtmlTestresultOutputNone*(outfile: File) = +

        No output details

        +#end proc +#proc generateHtmlTestresultPanelEnd*(outfile: File) = +
        +
        +#end proc +#proc generateHtmlAllTestsEnd*(outfile: File) = +
        +#end proc +#proc generateHtmlEnd*(outfile: File, timestamp: string) = +
        +
        +

        + Report generated by: testament – Nim Tester +
        + Made with Nim. Generated on: %timestamp +

        +
        +
        + + diff --git a/testament/testamenthtml.templ b/testament/testamenthtml.templ deleted file mode 100644 index 9190f370e..000000000 --- a/testament/testamenthtml.templ +++ /dev/null @@ -1,297 +0,0 @@ -#? stdtmpl(subsChar = '%', metaChar = '#', emit = "outfile.write") -#import strutils -# -#proc htmlQuote*(raw: string): string = -# result = raw.multiReplace( -# ("&", "&"), -# ("\"", """), -# ("'", "'"), -# ("<", "<"), -# (">", ">") -# ) -# -#end proc -#proc generateHtmlBegin*(outfile: File) = - - - - - Testament Test Results - - - - - - - -
        -

        Testament Test Results Nim Tester

        -#end proc -#proc generateHtmlAllTestsBegin*(outfile: File, machine, commit, branch: string, -# totalCount: BiggestInt, -# successCount: BiggestInt, successPercentage: string, -# ignoredCount: BiggestInt, ignoredPercentage: string, -# failedCount: BiggestInt, failedPercentage: string, onlyFailing = false) = -
        -
        Hostname
        -
        %machine
        -
        Git Commit
        -
        %commit
        -
        Branch ref.
        -
        %branch
        -
        -
        -
        All Tests
        -
        - - %totalCount -
        -
        Successful Tests
        -
        - - %successCount (%successPercentage) -
        -
        Skipped Tests
        -
        - - %ignoredCount (%ignoredPercentage) -
        -
        Failed Tests
        -
        - - %failedCount (%failedPercentage) -
        -
        -
        - -# if not onlyFailing: - - - - - - - - -# end if - - - - - - - - -
        All Tests -
        - - - - -
        -
        Successful Tests -
        - - - - -
        -
        Skipped Tests -
        - - - - -
        -
        Failed Tests -
        - - - - -
        -
        -
        -
        -#end proc -#proc generateHtmlTestresultPanelBegin*(outfile: File, trId, name, target, category, -# action, resultDescription, timestamp, result, resultSign, -# panelCtxClass, textCtxClass, bgCtxClass: string) = -
        - -
        -
        -
        Name
        -
        %name
        -
        Category
        -
        %category
        -
        Timestamp
        -
        %timestamp
        -
        Nim Action
        -
        %action
        -
        Nim Backend Target
        -
        %target
        -
        Code
        -
        %result
        -
        -#end proc -#proc generateHtmlTestresultOutputDetails*(outfile: File, expected, gotten: string) = -
        - - - - - - - - - - - - - -
        ExpectedActual
        %expected
        %gotten
        -
        -#end proc -#proc generateHtmlTestresultOutputNone*(outfile: File) = -

        No output details

        -#end proc -#proc generateHtmlTestresultPanelEnd*(outfile: File) = -
        -
        -#end proc -#proc generateHtmlAllTestsEnd*(outfile: File) = -
        -#end proc -#proc generateHtmlEnd*(outfile: File, timestamp: string) = -
        -
        -

        - Report generated by: testament – Nim Tester -
        - Made with Nim. Generated on: %timestamp -

        -
        -
        - - -- cgit 1.4.1-2-gfad0 From 825e08b04603a3839ba6d0b915aedfb08418b7d3 Mon Sep 17 00:00:00 2001 From: Araq Date: Mon, 14 Jan 2019 12:15:29 +0100 Subject: fixes #7524 --- compiler/parampatterns.nim | 15 +++++++-------- compiler/semtypes.nim | 7 ++++++- tests/trmacros/trmacros_various2.nim | 10 ++++++++++ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/compiler/parampatterns.nim b/compiler/parampatterns.nim index bbaf7a069..db79e3eb9 100644 --- a/compiler/parampatterns.nim +++ b/compiler/parampatterns.nim @@ -115,20 +115,19 @@ proc compileConstraints(p: PNode, result: var TPatternCode; conf: ConfigRef) = else: patternError(p, conf) -proc semNodeKindConstraints*(p: PNode; conf: ConfigRef): PNode = +proc semNodeKindConstraints*(n: PNode; conf: ConfigRef; start: Natural): PNode = ## does semantic checking for a node kind pattern and compiles it into an ## efficient internal format. - assert p.kind == nkCurlyExpr - result = newNodeI(nkStrLit, p.info) + result = newNodeI(nkStrLit, n.info) result.strVal = newStringOfCap(10) result.strVal.add(chr(aqNone.ord)) - if p.len >= 2: - for i in 1..= 2: + for i in start.. MaxStackSize-1: - internalError(conf, p.info, "parameter pattern too complex") + internalError(conf, n.info, "parameter pattern too complex") else: - patternError(p, conf) + patternError(n, conf) result.strVal.add(ppEof) type diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index f4936a71a..7056eab5f 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1028,7 +1028,12 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, proc semParamType(c: PContext, n: PNode, constraint: var PNode): PType = if n.kind == nkCurlyExpr: result = semTypeNode(c, n.sons[0], nil) - constraint = semNodeKindConstraints(n, c.config) + constraint = semNodeKindConstraints(n, c.config, 1) + elif n.kind == nkCall and + n[0].kind in {nkIdent, nkSym, nkOpenSymChoice, nkClosedSymChoice} and + considerQuotedIdent(c, n[0]).s == "{}": + result = semTypeNode(c, n[1], nil) + constraint = semNodeKindConstraints(n, c.config, 2) else: result = semTypeNode(c, n, nil) diff --git a/tests/trmacros/trmacros_various2.nim b/tests/trmacros/trmacros_various2.nim index d500c49de..c1367cb1b 100644 --- a/tests/trmacros/trmacros_various2.nim +++ b/tests/trmacros/trmacros_various2.nim @@ -77,3 +77,13 @@ block tstar: # check that it's been optimized properly: doAssert calls == 1 + +# bug #7524 +template in_to_out(typIn, typOut: typedesc) = + proc to_out(x: typIn{lit}): typOut = result = ord(x) + +# Generating the proc via template doesn't work +in_to_out(char, int) + +# This works +proc to_out2(x: char{lit}): int = result = ord(x) -- cgit 1.4.1-2-gfad0 From d69a7842fa0836ca18b8057a33bca81a771a9e5a Mon Sep 17 00:00:00 2001 From: Araq Date: Mon, 14 Jan 2019 12:36:34 +0100 Subject: fixes #7878 --- compiler/ccgexprs.nim | 2 +- tests/stdlib/trepr.nim | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index d00371dd8..ed6255004 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -1501,7 +1501,7 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) = addrLoc(p.config, a), genTypeInfo(p.module, t, e.info)]), a.storage) of tyOpenArray, tyVarargs: var b: TLoc - case a.t.kind + case skipTypes(a.t, abstractVarRange).kind of tyOpenArray, tyVarargs: putIntoDest(p, b, e, "$1, $1Len_0" % [rdLoc(a)], a.storage) of tyString, tySequence: diff --git a/tests/stdlib/trepr.nim b/tests/stdlib/trepr.nim index 33cb581ef..c1941bd38 100644 --- a/tests/stdlib/trepr.nim +++ b/tests/stdlib/trepr.nim @@ -1,5 +1,6 @@ discard """ - output: "{a, b}{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}" + output: '''{a, b}{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} +[1, 2, 3, 4, 5, 6]''' """ type @@ -25,3 +26,11 @@ when false: # "a", "b", "c", "d", "e" #] #echo(repr(testseq)) + +# bug #7878 +proc test(variable: var openarray[int]) = + echo repr(variable) + +var arr = [1, 2, 3, 4, 5, 6] + +test(arr) -- cgit 1.4.1-2-gfad0 From b78af990b8f656cb17044c96609f58e1f01e6a01 Mon Sep 17 00:00:00 2001 From: Arne Döring Date: Mon, 14 Jan 2019 17:16:17 +0100 Subject: Fixes #10065 (#10260) CountTable now returns 0 instead of 'key not found' for get requests. --- lib/pure/collections/tables.nim | 97 ++++++++++++++++++----------------------- tests/stdlib/tmget.nim | 8 ++-- 2 files changed, 47 insertions(+), 58 deletions(-) diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index f46a368b1..1fa2ca0a6 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -933,52 +933,6 @@ proc rawGet[A](t: CountTable[A], key: A): int = h = nextTry(h, high(t.data)) result = -1 - h # < 0 => MISSING; insert idx = -1 - result -template ctget(t, key: untyped): untyped = - var index = rawGet(t, key) - if index >= 0: result = t.data[index].val - else: - when compiles($key): - raise newException(KeyError, "key not found: " & $key) - else: - raise newException(KeyError, "key not found") - -proc `[]`*[A](t: CountTable[A], key: A): int {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. If ``key`` is not in ``t``, - ## the ``KeyError`` exception is raised. One can check with ``hasKey`` - ## whether the key exists. - ctget(t, key) - -proc `[]`*[A](t: var CountTable[A], key: A): var int {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ctget(t, key) - -proc mget*[A](t: var CountTable[A], key: A): var int {.deprecated.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. - ctget(t, key) - -proc getOrDefault*[A](t: CountTable[A], key: A): int = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, 0 (the - ## default initialization value of ``int``), is returned. - var index = rawGet(t, key) - if index >= 0: result = t.data[index].val - -proc getOrDefault*[A](t: CountTable[A], key: A, default: int): int = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, the - ## integer value of ``default`` is returned. - var index = rawGet(t, key) - result = if index >= 0: t.data[index].val else: default - -proc hasKey*[A](t: CountTable[A], key: A): bool = - ## returns true iff ``key`` is in the table ``t``. - result = rawGet(t, key) >= 0 - -proc contains*[A](t: CountTable[A], key: A): bool = - ## Alias of ``hasKey`` for use with the ``in`` operator. - return hasKey[A](t, key) - proc rawInsert[A](t: CountTable[A], data: var seq[tuple[key: A, val: int]], key: A, val: int) = var h: Hash = hash(key) and high(data) @@ -996,16 +950,40 @@ proc enlarge[A](t: var CountTable[A]) = proc `[]=`*[A](t: var CountTable[A], key: A, val: int) = ## puts a ``(key, value)`` pair into ``t``. assert val >= 0 - var h = rawGet(t, key) + let h = rawGet(t, key) if h >= 0: t.data[h].val = val else: if mustRehash(len(t.data), t.counter): enlarge(t) rawInsert(t, t.data, key, val) inc(t.counter) - #h = -1 - h - #t.data[h].key = key - #t.data[h].val = val + +template ctget(t, key, default: untyped): untyped = + var index = rawGet(t, key) + result = if index >= 0: t.data[index].val else: default + +proc `[]`*[A](t: CountTable[A], key: A): int = + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. + ## Otherwise ``0`` is returned. + ctget(t, key, 0) + +proc mget*[A](t: var CountTable[A], key: A): var int = + ## Retrieves the value at ``t[key]``. The value can be modified. + ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + get(t, key) + +proc getOrDefault*[A](t: CountTable[A], key: A; default: int = 0): int = + ## Retrieves the value at ``t[key]`` if``key`` is in ``t``. Otherwise, the + ## integer value of ``default`` is returned. + ctget(t, key, default) + +proc hasKey*[A](t: CountTable[A], key: A): bool = + ## returns true iff ``key`` is in the table ``t``. + result = rawGet(t, key) >= 0 + +proc contains*[A](t: CountTable[A], key: A): bool = + ## Alias of ``hasKey`` for use with the ``in`` operator. + return hasKey[A](t, key) proc inc*[A](t: var CountTable[A], key: A, val = 1) = ## increments ``t[key]`` by ``val``. @@ -1113,16 +1091,15 @@ iterator mvalues*[A](t: CountTableRef[A]): var int = for h in 0..high(t.data): if t.data[h].val != 0: yield t.data[h].val -proc `[]`*[A](t: CountTableRef[A], key: A): var int {.deprecatedGet.} = +proc `[]`*[A](t: CountTableRef[A], key: A): int = ## retrieves the value at ``t[key]``. The value can be modified. ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. result = t[][key] -proc mget*[A](t: CountTableRef[A], key: A): var int {.deprecated.} = +proc mget*[A](t: CountTableRef[A], key: A): var int = ## retrieves the value at ``t[key]``. The value can be modified. ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. - result = t[][key] + mget(t[], key) proc getOrDefault*[A](t: CountTableRef[A], key: A): int = ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, 0 (the @@ -1394,6 +1371,18 @@ when isMainModule: let t = toCountTable([0, 0, 5, 5, 5]) doAssert t.smallest == (0, 2) + block: #10065 + let t = toCountTable("abracadabra") + doAssert t['z'] == 0 + + var t_mut = toCountTable("abracadabra") + doAssert t_mut['z'] == 0 + # the previous read may not have modified the table. + doAssert t_mut.hasKey('z') == false + t_mut['z'] = 1 + doAssert t_mut['z'] == 1 + doAssert t_mut.hasKey('z') == true + block: var tp: Table[string, string] = initTable[string, string]() doAssert "test1" == tp.getOrDefault("test1", "test1") diff --git a/tests/stdlib/tmget.nim b/tests/stdlib/tmget.nim index 5792b6282..5e2e327f4 100644 --- a/tests/stdlib/tmget.nim +++ b/tests/stdlib/tmget.nim @@ -11,10 +11,10 @@ Can't access 6 Can't access 6 10 11 -Can't access 6 +0 10 11 -Can't access 6 +0 10 11 Can't access 6 @@ -85,7 +85,7 @@ block: except KeyError: echo "Can't access 6" echo x[5] - x[5] += 1 + x.inc 5, 1 var c = x[5] echo c @@ -97,7 +97,7 @@ block: except KeyError: echo "Can't access 6" echo x[5] - x[5] += 1 + x.inc 5, 1 var c = x[5] echo c -- cgit 1.4.1-2-gfad0 From 1899d8d107e2f5d991fd3cad321c959768bdf8c8 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Mon, 14 Jan 2019 20:11:33 +0100 Subject: make tests green again --- tests/metatype/tmetatype_issues.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/metatype/tmetatype_issues.nim b/tests/metatype/tmetatype_issues.nim index 08d486de2..c184689a1 100644 --- a/tests/metatype/tmetatype_issues.nim +++ b/tests/metatype/tmetatype_issues.nim @@ -9,6 +9,7 @@ asd Foo Bar ''' +joinable: false """ import typetraits, macros -- cgit 1.4.1-2-gfad0 From dd1f23f6fc9c3d57f5d3e2a44d2abe4dc1b57a2a Mon Sep 17 00:00:00 2001 From: genotrance Date: Mon, 14 Jan 2019 15:16:11 -0600 Subject: Expose CTime (#10301) --- lib/pure/times.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 8fe04a4b9..166dfd446 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -217,7 +217,7 @@ when defined(JS): elif defined(posix): import posix - type CTime = posix.Time + type CTime* = posix.Time var realTimeClockId {.importc: "CLOCK_REALTIME", header: "".}: Clockid @@ -236,10 +236,10 @@ elif defined(windows): import winlean when defined(i386) and defined(gcc): - type CTime {.importc: "time_t", header: "".} = distinct int32 + type CTime* {.importc: "time_t", header: "".} = distinct int32 else: # newest version of Visual C++ defines time_t to be of 64 bits - type CTime {.importc: "time_t", header: "".} = distinct int64 + type CTime* {.importc: "time_t", header: "".} = distinct int64 # visual c's c runtime exposes these under a different name var timezone {.importc: "_timezone", header: "".}: int -- cgit 1.4.1-2-gfad0 From e17321aa2429c6bed97bef28a149fd21166b90a2 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Sat, 12 Jan 2019 15:51:44 -0800 Subject: improve formatting in assertEquals --- compiler/unittest_light.nim | 15 ++++++++------- tests/compiler/tunittest_light.nim | 22 +++++++++++----------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/compiler/unittest_light.nim b/compiler/unittest_light.nim index bcba6f7c7..d9842b399 100644 --- a/compiler/unittest_light.nim +++ b/compiler/unittest_light.nim @@ -14,8 +14,8 @@ proc mismatch*[T](lhs: T, rhs: T): string = proc quoted(s: string): string = result.addQuoted s result.add "\n" - result.add "lhs:{\n" & replaceInvisible( - $lhs) & "}\nrhs:{\n" & replaceInvisible($rhs) & "}\n" + result.add "lhs:{" & replaceInvisible( + $lhs) & "}\nrhs:{" & replaceInvisible($rhs) & "}\n" when compiles(lhs.len): if lhs.len != rhs.len: result.add "lhs.len: " & $lhs.len & " rhs.len: " & $rhs.len & "\n" @@ -26,12 +26,13 @@ proc mismatch*[T](lhs: T, rhs: T): string = i.inc result.add "first mismatch index: " & $i & "\n" if i < lhs.len and i < rhs.len: - result.add "lhs[i]: {" & quoted($lhs[i]) & "} rhs[i]: {" & quoted( - $rhs[i]) & "}" - result.add "lhs[0.. Date: Sat, 12 Jan 2019 15:26:22 -0800 Subject: fix #9842 #9951: `nim -r` and parseopt.cmdLineRest are now correct --- compiler/lineinfos.nim | 2 +- lib/pure/parseopt.nim | 24 +-------- tests/misc/tparseopt.nim | 136 ++++++++++++++++++++++++++++++++--------------- 3 files changed, 94 insertions(+), 68 deletions(-) diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index 21ce44406..165d75821 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -171,7 +171,7 @@ proc computeNotesVerbosity(): array[0..3, TNoteKinds] = warnGcUnsafe, hintPath, hintDependency, hintCodeBegin, hintCodeEnd, hintSource, hintGlobalVar, hintGCStats} result[0] = result[1] - {hintSuccessX, hintSuccess, hintConf, - hintProcessing, hintPattern, hintExecuting, hintLinking} + hintProcessing, hintPattern, hintExecuting, hintLinking, hintCC} const NotesVerbosity* = computeNotesVerbosity() diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim index eba915604..06f32f032 100644 --- a/lib/pure/parseopt.nim +++ b/lib/pure/parseopt.nim @@ -73,24 +73,6 @@ proc parseWord(s: string, i: int, w: var string, inc(result) when declared(os.paramCount): - proc quote(s: string): string = - if find(s, {' ', '\t'}) >= 0 and s.len > 0 and s[0] != '"': - if s[0] == '-': - result = newStringOfCap(s.len) - var i = parseWord(s, 0, result, {' ', '\t', ':', '='}) - if i < s.len and s[i] in {':','='}: - result.add s[i] - inc i - result.add '"' - while i < s.len: - result.add s[i] - inc i - result.add '"' - else: - result = '"' & s & '"' - else: - result = s - # we cannot provide this for NimRtl creation on Posix, because we can't # access the command line arguments then! @@ -228,11 +210,7 @@ proc next*(p: var OptParser) {.rtl, extern: "npo$1".} = when declared(os.paramCount): proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo$1".} = ## retrieves the rest of the command line that has not been parsed yet. - var res = "" - for i in p.idx.. p.idx: res.add ' ' - res.add quote(p.cmds[i]) - result = res.TaintedString + result = p.cmds[p.idx .. ^1].quoteShellCommand.TaintedString proc remainingArgs*(p: OptParser): seq[TaintedString] {.rtl, extern: "npo$1".} = ## retrieves the rest of the command line that has not been parsed yet. diff --git a/tests/misc/tparseopt.nim b/tests/misc/tparseopt.nim index cbed5d476..39c17869c 100644 --- a/tests/misc/tparseopt.nim +++ b/tests/misc/tparseopt.nim @@ -30,54 +30,102 @@ kind: cmdLongOption key:val -- debug:3 kind: cmdShortOption key:val -- l:4 kind: cmdShortOption key:val -- r:2''' """ -from parseopt import nil -from parseopt2 import nil +when defined(testament_tparseopt): + import os + proc main() = + let args = commandLineParams() + echo args + for i, ai in args: + echo "arg ", i, " ai.len:", ai.len, " :{", ai, "}" + main() +else: + from parseopt import nil + from parseopt2 import nil -block: - echo "parseopt" - for kind, key, val in parseopt.getopt(): - echo "kind: ", kind, "\tkey:val -- ", key, ":", val + block: + echo "parseopt" + for kind, key, val in parseopt.getopt(): + echo "kind: ", kind, "\tkey:val -- ", key, ":", val - # pass custom cmdline arguments - echo "first round" - var argv = "--left --debug:3 -l=4 -r:2" - var p = parseopt.initOptParser(argv) - for kind, key, val in parseopt.getopt(p): - echo "kind: ", kind, "\tkey:val -- ", key, ":", val - break - # reset getopt iterator and check arguments are returned correctly. - echo "second round" - for kind, key, val in parseopt.getopt(p): - echo "kind: ", kind, "\tkey:val -- ", key, ":", val + # pass custom cmdline arguments + echo "first round" + var argv = "--left --debug:3 -l=4 -r:2" + var p = parseopt.initOptParser(argv) + for kind, key, val in parseopt.getopt(p): + echo "kind: ", kind, "\tkey:val -- ", key, ":", val + break + # reset getopt iterator and check arguments are returned correctly. + echo "second round" + for kind, key, val in parseopt.getopt(p): + echo "kind: ", kind, "\tkey:val -- ", key, ":", val - # bug #9619 - var x = parseopt.initOptParser(@["--foo:", "--path"], allowWhitespaceAfterColon = false) - for kind, key, val in parseopt.getopt(x): - echo kind, " ", key + # bug #9619 + var x = parseopt.initOptParser(@["--foo:", "--path"], + allowWhitespaceAfterColon = false) + for kind, key, val in parseopt.getopt(x): + echo kind, " ", key -block: - echo "parseoptNoVal" - # test NoVal mode with custom cmdline arguments - var argv = "--left --debug:3 -l -r:2 --debug 2 --debug=1 -r1 -r=0 -lr4" - var p = parseopt.initOptParser(argv, - shortNoVal = {'l'}, longNoVal = @["left"]) - for kind, key, val in parseopt.getopt(p): - echo "kind: ", kind, "\tkey:val -- ", key, ":", val + block: + echo "parseoptNoVal" + # test NoVal mode with custom cmdline arguments + var argv = "--left --debug:3 -l -r:2 --debug 2 --debug=1 -r1 -r=0 -lr4" + var p = parseopt.initOptParser(argv, + shortNoVal = {'l'}, longNoVal = @["left"]) + for kind, key, val in parseopt.getopt(p): + echo "kind: ", kind, "\tkey:val -- ", key, ":", val -block: - echo "parseopt2" - for kind, key, val in parseopt2.getopt(): - echo "kind: ", kind, "\tkey:val -- ", key, ":", val + block: + echo "parseopt2" + for kind, key, val in parseopt2.getopt(): + echo "kind: ", kind, "\tkey:val -- ", key, ":", val - # pass custom cmdline arguments - echo "first round" - var argv: seq[string] = @["--left", "--debug:3", "-l=4", "-r:2"] - var p = parseopt2.initOptParser(argv) - for kind, key, val in parseopt2.getopt(p): - echo "kind: ", kind, "\tkey:val -- ", key, ":", val - break - # reset getopt iterator and check arguments are returned correctly. - echo "second round" - for kind, key, val in parseopt2.getopt(p): - echo "kind: ", kind, "\tkey:val -- ", key, ":", val + # pass custom cmdline arguments + echo "first round" + var argv: seq[string] = @["--left", "--debug:3", "-l=4", "-r:2"] + var p = parseopt2.initOptParser(argv) + for kind, key, val in parseopt2.getopt(p): + echo "kind: ", kind, "\tkey:val -- ", key, ":", val + break + # reset getopt iterator and check arguments are returned correctly. + echo "second round" + for kind, key, val in parseopt2.getopt(p): + echo "kind: ", kind, "\tkey:val -- ", key, ":", val + + import osproc, os, strutils + from stdtest/specialpaths import buildDir + import "../.." / compiler/unittest_light + + block: # fix #9951 + var p = parseopt.initOptParser(@["echo \"quoted\""]) + assertEquals parseopt.cmdLineRest(p), """'echo "quoted"'""" + let args = @["a1b", "a2 b", "", "a4\"b", "a5'b", r"a6\b", "a7\'b"] + var p2 = parseopt.initOptParser(args) + assertEquals parseopt.cmdLineRest(p2), + """a1b 'a2 b' '' 'a4"b' 'a5'"'"'b' 'a6\b' 'a7'"'"'b'""" + doAssert "a5'b" == "a5\'b" + + block: # fix #9842 + let exe = buildDir / "D20190112T145450".addFileExt(ExeExt) + defer: removeFile exe + let args = @["a1b", "a2 b", "", "a4\"b", "a5'b", r"a6\b", "a7\'b"] + let cmd = "$# c -r --verbosity:0 -o:$# -d:testament_tparseopt $# $#" % + [getCurrentCompilerExe(), exe, currentSourcePath(), + args.quoteShellCommand] + var ret = execCmdEx(cmd, options = {}) + if ret.exitCode != 0: + # before bug fix, running cmd would show: + # sh: -c: line 0: unexpected EOF while looking for matching `"'\n + echo "exitCode: ", ret.exitCode, " cmd:", cmd + doAssert false + stripLineEnd(ret.output) + assertEquals ret.output, + """ +@["a1b", "a2 b", "", "a4\"b", "a5\'b", "a6\\b", "a7\'b"] +arg 0 ai.len:3 :{a1b} +arg 1 ai.len:4 :{a2 b} +arg 2 ai.len:0 :{} +arg 3 ai.len:4 :{a4"b} +arg 4 ai.len:4 :{a5'b} +arg 5 ai.len:4 :{a6\b} +arg 6 ai.len:4 :{a7'b}""" -- cgit 1.4.1-2-gfad0 From 9e68b2ce5d836fc269be9735d97aebb99adbfa2d Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Sun, 13 Jan 2019 23:53:50 -0800 Subject: fix test --- tests/misc/tparseopt.nim | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/misc/tparseopt.nim b/tests/misc/tparseopt.nim index 39c17869c..57253389d 100644 --- a/tests/misc/tparseopt.nim +++ b/tests/misc/tparseopt.nim @@ -29,6 +29,7 @@ kind: cmdLongOption key:val -- left: kind: cmdLongOption key:val -- debug:3 kind: cmdShortOption key:val -- l:4 kind: cmdShortOption key:val -- r:2''' +joinable: false """ when defined(testament_tparseopt): @@ -98,12 +99,22 @@ else: block: # fix #9951 var p = parseopt.initOptParser(@["echo \"quoted\""]) - assertEquals parseopt.cmdLineRest(p), """'echo "quoted"'""" + let expected = when defined(windows): + """"echo \"quoted\""""" + else: + """'echo "quoted"'""" + assertEquals parseopt.cmdLineRest(p), expected + + doAssert "a5'b" == "a5\'b" + let args = @["a1b", "a2 b", "", "a4\"b", "a5'b", r"a6\b", "a7\'b"] var p2 = parseopt.initOptParser(args) - assertEquals parseopt.cmdLineRest(p2), + let expected2 = when defined(windows): + """a1b "a2 b" "" a4\"b a5'b a6\b a7'b""" + else: """a1b 'a2 b' '' 'a4"b' 'a5'"'"'b' 'a6\b' 'a7'"'"'b'""" doAssert "a5'b" == "a5\'b" + assertEquals parseopt.cmdLineRest(p2), expected2 block: # fix #9842 let exe = buildDir / "D20190112T145450".addFileExt(ExeExt) -- cgit 1.4.1-2-gfad0 From c8ba9ffdba9165b2c952f579005fdbb6871a1380 Mon Sep 17 00:00:00 2001 From: Jjp137 Date: Mon, 14 Jan 2019 22:05:05 -0800 Subject: lib.rst: fix a typo (#10308) [ci skip] --- doc/lib.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/lib.rst b/doc/lib.rst index 395b242e6..1f19f9bf4 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -107,7 +107,7 @@ String handling substrings, replacing substrings. * `strformat `_ - Macro based standard string interpolation / formatting. Inpired by + Macro based standard string interpolation / formatting. Inspired by Python's ``f``-strings. * `strmisc `_ -- cgit 1.4.1-2-gfad0 From 8922063bd84e9109a5ba493188f201ebda74f66d Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Tue, 15 Jan 2019 12:38:12 +0530 Subject: typed/untyped return type is invalid for everything except templates and macros (#10275) --- compiler/semstmts.nim | 3 --- compiler/semtypes.nim | 7 +++++-- tests/proc/tillegalreturntype.nim | 12 ++++++++++++ 3 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 tests/proc/tillegalreturntype.nim diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 12283e042..3fdbb85db 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1253,9 +1253,6 @@ proc semTypeSection(c: PContext, n: PNode): PNode = proc semParamList(c: PContext, n, genericParams: PNode, s: PSym) = s.typ = semProcTypeNode(c, n, genericParams, nil, s.kind) - if s.kind notin {skMacro, skTemplate}: - if s.typ.sons[0] != nil and s.typ.sons[0].kind == tyStmt: - localError(c.config, n.info, "invalid return type: 'stmt'") proc addParams(c: PContext, n: PNode, kind: TSymKind) = for i in countup(1, sonsLen(n)-1): diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 7056eab5f..ddc42c5b4 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1158,12 +1158,15 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, # turn explicit 'void' return type into 'nil' because the rest of the # compiler only checks for 'nil': if skipTypes(r, {tyGenericInst, tyAlias, tySink}).kind != tyVoid: + if kind notin {skMacro, skTemplate} and r.kind in {tyStmt, tyExpr}: + localError(c.config, n.sons[0].info, "return type '" & typeToString(r) & + "' is only valid for macros and templates") # 'auto' as a return type does not imply a generic: - if r.kind == tyAnything: + elif r.kind == tyAnything: # 'p(): auto' and 'p(): expr' are equivalent, but the rest of the # compiler is hardly aware of 'auto': r = newTypeS(tyExpr, c) - elif r.kind != tyExpr: + else: if r.sym == nil or sfAnon notin r.sym.flags: let lifted = liftParamType(c, kind, genericParams, r, "result", n.sons[0].info) diff --git a/tests/proc/tillegalreturntype.nim b/tests/proc/tillegalreturntype.nim new file mode 100644 index 000000000..be9e2147e --- /dev/null +++ b/tests/proc/tillegalreturntype.nim @@ -0,0 +1,12 @@ +discard """ + cmd: "nim check $file" + errmsg: "" + nimout: '''tillegalreturntype.nim(8, 11) Error: return type 'typed' is only valid for macros and templates +tillegalreturntype.nim(11, 11) Error: return type 'untyped' is only valid for macros and templates''' +""" + +proc x(): typed = + discard + +proc y(): untyped = + discard -- cgit 1.4.1-2-gfad0 From 1d941b87352dc675fdedac274ddf0b9d43c8d5c5 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 15 Jan 2019 00:53:58 -0800 Subject: refs #10249 #10266; each command logged only if --verboseCommands:true (#10303) --- testament/tester.nim | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/testament/tester.nim b/testament/tester.nim index 04552b7c9..ad0d22742 100644 --- a/testament/tester.nim +++ b/testament/tester.nim @@ -18,6 +18,7 @@ var useColors = true var backendLogging = true var simulate = false var verboseMegatest = false # very verbose but can be useful +var verboseCommands = false const testsDir = "tests" & DirSep @@ -37,6 +38,7 @@ Arguments: Options: --print also print results to the console --verboseMegatest log to stdout megatetest compilation + --verboseCommands log to stdout info about commands being run --simulate see what tests would be run but don't run them (for debugging) --failing only show failing/ignored tests --targets:"c c++ js objc" run tests for specified targets (default: all) @@ -111,12 +113,14 @@ proc execCmdEx2(command: string, args: openarray[string], options: set[ProcessOp result.exitCode = peekExitCode(p) if result.exitCode != -1: break close(p) - if result.exitCode != 0: + + if verboseCommands: var command2 = command if args.len > 0: command2.add " " & args.quoteShellCommand - echo (msg: "execCmdEx2 failed", + echo (msg: "execCmdEx2", command: command2, - options: options) + options: options, + exitCode: result.exitCode) proc nimcacheDir(filename, options: string, target: TTarget): string = ## Give each test a private nimcache dir so they don't clobber each other's. @@ -540,6 +544,7 @@ proc main() = case p.key.string.normalize of "print", "verbose": optPrintResults = true of "verbosemegatest": verboseMegatest = true + of "verbosecommands": verboseCommands = true of "failing": optFailing = true of "pedantic": discard "now always enabled" of "targets": -- cgit 1.4.1-2-gfad0 From fbf9b1f88af5f56e095574b6d3bddbe4246b0a16 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 15 Jan 2019 10:01:45 +0100 Subject: fixed indentation --- testament/categories.nim | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/testament/categories.nim b/testament/categories.nim index 4ff50af97..54b4272ee 100644 --- a/testament/categories.nim +++ b/testament/categories.nim @@ -600,7 +600,7 @@ proc runJoinedTest(r: var TResults, cat: Category, testsDir: string) = get from Nim cmd put outputGotten.txt, outputGotten.txt, megatest.nim there too delete upon completion, maybe - ]# + ]# var outDir = nimcacheDir(testsDir / "megatest", "", targetC) const marker = "megatest:processing: " @@ -707,10 +707,9 @@ proc processCategory(r: var TResults, cat: Category, options, testsDir: string, runJoinedTest(r, cat, testsDir) else: var testsRun = 0 - var files: seq[string] for file in walkDirRec(testsDir &.? cat.string): - if isTestFile(file): files.add file + if isTestFile(file): files.add file files.sort # give reproducible order for i, name in files: -- cgit 1.4.1-2-gfad0 From 06a8b488111b6d3858706bec6b31b1e4609620ea Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 15 Jan 2019 10:01:57 +0100 Subject: disable one more test for C++ --- tests/iter/titervaropenarray.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/iter/titervaropenarray.nim b/tests/iter/titervaropenarray.nim index 701f652df..4469fdcf5 100644 --- a/tests/iter/titervaropenarray.nim +++ b/tests/iter/titervaropenarray.nim @@ -1,5 +1,6 @@ discard """ output: "123" + targets: "C" """ # Try to break the transformation pass: iterator iterAndZero(a: var openArray[int]): int = -- cgit 1.4.1-2-gfad0 From 05c52ff34f996bb44425bcc17ae3ae60ed896bef Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 15 Jan 2019 10:15:27 +0100 Subject: fixes #10203 (#10290) * fixes #10203 * make typredef test green again * fixes the regressions differently --- compiler/semtypes.nim | 28 +++++++++++++--------------- compiler/types.nim | 2 +- tests/objects/tobject.nim | 20 ++++++++++++++++++++ 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index ddc42c5b4..c28902b1f 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1318,7 +1318,19 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = if tx != result and tx.kind == tyObject and tx.sons[0] != nil: semObjectTypeForInheritedGenericInst(c, n, tx) -proc maybeAliasType(c: PContext; typeExpr, prev: PType): PType +proc maybeAliasType(c: PContext; typeExpr, prev: PType): PType = + if typeExpr.kind in {tyObject, tyEnum, tyDistinct, tyForward} and prev != nil: + result = newTypeS(tyAlias, c) + result.rawAddSon typeExpr + result.sym = prev.sym + assignType(prev, result) + +proc fixupTypeOf(c: PContext, prev: PType, typExpr: PNode) = + if prev != nil: + let result = newTypeS(tyAlias, c) + result.rawAddSon typExpr.typ + result.sym = prev.sym + assignType(prev, result) proc semTypeExpr(c: PContext, n: PNode; prev: PType): PType = var n = semExprWithType(c, n, {efDetermineType}) @@ -1422,20 +1434,6 @@ proc semProcTypeWithScope(c: PContext, n: PNode, when useEffectSystem: setEffectsForProcType(c.graph, result, n.sons[1]) closeScope(c) -proc maybeAliasType(c: PContext; typeExpr, prev: PType): PType = - if typeExpr.kind in {tyObject, tyEnum, tyDistinct} and prev != nil: - result = newTypeS(tyAlias, c) - result.rawAddSon typeExpr - result.sym = prev.sym - assignType(prev, result) - -proc fixupTypeOf(c: PContext, prev: PType, typExpr: PNode) = - if prev != nil: - let result = newTypeS(tyAlias, c) - result.rawAddSon typExpr.typ - result.sym = prev.sym - assignType(prev, result) - proc symFromExpectedTypeNode(c: PContext, n: PNode): PSym = if n.kind == nkType: result = symFromType(c, n.typ, n.info) diff --git a/compiler/types.nim b/compiler/types.nim index 5b8b9e602..797336ddf 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -432,7 +432,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = sfAnon notin t.sym.flags: if t.kind == tyInt and isIntLit(t): result = t.sym.name.s & " literal(" & $t.n.intVal & ")" - elif t.kind == tyAlias: + elif t.kind == tyAlias and t.sons[0].kind != tyAlias: result = typeToString(t.sons[0]) elif prefer in {preferName, preferTypeName} or t.sym.owner.isNil: result = t.sym.name.s diff --git a/tests/objects/tobject.nim b/tests/objects/tobject.nim index 61ef7442e..fbf531c3d 100644 --- a/tests/objects/tobject.nim +++ b/tests/objects/tobject.nim @@ -17,3 +17,23 @@ suite "object basic methods": check($obj == "(foo: 1)") test "it should test equality based on fields": check(makeObj(1) == makeObj(1)) + +# bug #10203 + +type + TMyObj = TYourObj + TYourObj = object of RootObj + x, y: int + +proc init: TYourObj = + result.x = 0 + result.y = -1 + +proc f(x: var TYourObj) = + discard + +var m: TMyObj = init() +f(m) + +var a: TYourObj = m +var b: TMyObj = a -- cgit 1.4.1-2-gfad0 From c05e9c7c736e471895794a397e677400822b0672 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 15 Jan 2019 05:46:01 -0800 Subject: fix twrong_refcounts in nim cpp mode (#10313) --- tests/parallel/twrong_refcounts.nim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/parallel/twrong_refcounts.nim b/tests/parallel/twrong_refcounts.nim index ac428762d..ed3c1b894 100644 --- a/tests/parallel/twrong_refcounts.nim +++ b/tests/parallel/twrong_refcounts.nim @@ -1,7 +1,10 @@ discard """ output: "Success" + target: "c" """ +# Note: target: "cpp" fails because we can't yet have `extern "C"` mangling in +# `exportc` procs. import math, random, threadPool # --- -- cgit 1.4.1-2-gfad0 From 9a92bc15b38b60a889815a1be9f87cca5038f747 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 15 Jan 2019 05:46:29 -0800 Subject: [CI] runCI runs `kochExec "boot"` regardless of nim mode (#10312) --- koch.nim | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/koch.nim b/koch.nim index a77ad3144..5e9537607 100644 --- a/koch.nim +++ b/koch.nim @@ -444,8 +444,7 @@ proc runCI(cmd: string) = # note(@araq): Do not replace these commands with direct calls (eg boot()) # as that would weaken our testing efforts. when defined(posix): # appveyor (on windows) didn't run this - # todo: implement `execWithEnv` - exec("env NIM_COMPILE_TO_CPP=false $1 boot" % kochExe.quoteShell) + kochExec "boot" kochExec "boot -d:release" ## build nimble early on to enable remainder to depend on it if needed -- cgit 1.4.1-2-gfad0 From 4355f23ee5fd53acfdaa8ecb5dbb50f9b43f98b2 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 15 Jan 2019 05:50:28 -0800 Subject: fix #10305 nim cpp is now nan-correct at CT (#10310) * fix #10305 nim cpp is now nan-correct at CT * add example where simply `nim cpp -d:release` would exhibit nan bug --- compiler/extccomp.nim | 8 ++++---- doc/nimc.rst | 3 +++ tests/float/tfloatnan.nim | 28 ++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index ef371d5d0..2c5af6433 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -63,8 +63,8 @@ compiler gcc: result = ( name: "gcc", objExt: "o", - optSpeed: " -O3 -ffast-math ", - optSize: " -Os -ffast-math ", + optSpeed: " -O3 ", + optSize: " -Os ", compilerExe: "gcc", cppCompiler: "g++", compileTmpl: "-c $options $include -o $objfile $file", @@ -88,8 +88,8 @@ compiler nintendoSwitchGCC: result = ( name: "switch_gcc", objExt: "o", - optSpeed: " -O3 -ffast-math ", - optSize: " -Os -ffast-math ", + optSpeed: " -O3 ", + optSize: " -Os ", compilerExe: "aarch64-none-elf-gcc", cppCompiler: "aarch64-none-elf-g++", compileTmpl: "-w -MMD -MP -MF $dfile -c $options $include -o $objfile $file", diff --git a/doc/nimc.rst b/doc/nimc.rst index 6f6d38d66..4ffb595c0 100644 --- a/doc/nimc.rst +++ b/doc/nimc.rst @@ -347,6 +347,9 @@ complete list. Define Effect ====================== ========================================================= ``release`` Turns off runtime checks and turns on the optimizer. + More aggressive optimizations are possible, eg: + ``--passC:-ffast-math`` (but see issue #10305) + ``--stacktrace:off`` ``useWinAnsi`` Modules like ``os`` and ``osproc`` use the Ansi versions of the Windows API. The default build uses the Unicode version. diff --git a/tests/float/tfloatnan.nim b/tests/float/tfloatnan.nim index 29937a862..8f384c3d9 100644 --- a/tests/float/tfloatnan.nim +++ b/tests/float/tfloatnan.nim @@ -14,3 +14,31 @@ echo "Nim: ", f32, " (float)" let f64: float64 = NaN echo "Nim: ", f64, " (double)" + +block: # issue #10305 + # with `-O3 -ffast-math`, generated C/C++ code is not nan compliant + # user can pass `--passC:-ffast-math` if he doesn't care. + proc fun() = + # this was previously failing at compile time with a nim compiler + # that was compiled with `nim cpp -d:release` + let a1 = 0.0 + let a = 0.0/a1 + let b1 = a == 0.0 + let b2 = a == a + doAssert not b1 + doAssert not b2 + + proc fun2(i: int) = + # this was previously failing simply with `nim cpp -d:release`; the + # difference with above example is that optimization (const folding) can't + # take place in this example to hide the non-compliant nan bug. + let a = 0.0/(i.float) + let b1 = a == 0.0 + let b2 = a == a + doAssert not b1 + doAssert not b2 + + static: fun() + fun() + fun2(0) + -- cgit 1.4.1-2-gfad0 From 06e3d3ab4dea4aa11a1728c849799ce6aeecb3a4 Mon Sep 17 00:00:00 2001 From: Reimer Behrends Date: Tue, 15 Jan 2019 19:00:02 +0100 Subject: Disable interior pointer checking by default for the Boehm GC. (#10316) The Boehm GC only needs interior pointer checking if pointers in global variables or on the heap point to the interior of an object rather than the beginning. If this does not happen, then checking for interior pointers causes additional overhead, in particular because any objects whose sizes are an exact multiple of two words gain another two words of padding, wasting memory. If checking of interior pointers is still desired, this can be achieved by setting the environment variable GC_ALL_INTERIOR_POINTERS. Pointers on the stack will always be treated as potential interior pointers, as compiler optimizations may advance the only live reference to a point past the beginning of an object. --- lib/system/mmdisp.nim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index 89bc11d2c..9cc7ab323 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -72,6 +72,8 @@ when defined(boehmgc): proc boehmGCincremental {. importc: "GC_enable_incremental", boehmGC.} proc boehmGCfullCollect {.importc: "GC_gcollect", boehmGC.} + proc boehmGC_set_all_interior_pointers(flag: cint) {. + importc: "GC_set_all_interior_pointers", boehmGC.} proc boehmAlloc(size: int): pointer {.importc: "GC_malloc", boehmGC.} proc boehmAllocAtomic(size: int): pointer {. importc: "GC_malloc_atomic", boehmGC.} @@ -148,6 +150,7 @@ when defined(boehmgc): proc nimGC_setStackBottom(theStackBottom: pointer) = discard proc initGC() = + boehmGC_set_all_interior_pointers(0) boehmGCinit() when hasThreadSupport: boehmGC_allow_register_threads() -- cgit 1.4.1-2-gfad0 From beed27b75d27118324d89b83974b7cf3065769cf Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 15 Jan 2019 10:02:01 -0800 Subject: improve vmgen.codeListing formatting (#10306) * improve vmgen.codeListing formatting * address comments --- compiler/dfa.nim | 2 +- compiler/vm.nim | 7 +++---- compiler/vmgen.nim | 31 ++++++++++++++++++++++--------- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/compiler/dfa.nim b/compiler/dfa.nim index cd32d95d5..df9584576 100644 --- a/compiler/dfa.nim +++ b/compiler/dfa.nim @@ -86,7 +86,7 @@ proc codeListing(c: ControlFlowGraph, result: var string, start=0; last = -1) = result.add("\n") inc i if i in jumpTargets: result.add("L" & $i & ": End\n") - + # consider calling `asciitables.alignTable` proc echoCfg*(c: ControlFlowGraph; start=0; last = -1) {.deprecated.} = ## echos the ControlFlowGraph for debugging purposes. diff --git a/compiler/vm.nim b/compiler/vm.nim index c001981f8..180f3800b 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -10,10 +10,6 @@ ## This file implements the new evaluation engine for Nim code. ## An instruction is 1-3 int32s in memory, it is a register based VM. -const - debugEchoCode = false - traceCode = debugEchoCode - import ast except getstr import @@ -26,6 +22,9 @@ from evaltempl import evalTemplate from modulegraphs import ModuleGraph, PPassContext +const + traceCode = debugEchoCode + when hasFFI: import evalffi diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index afadb4169..033cc81f0 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -33,6 +33,11 @@ import import platform from os import splitFile +const + debugEchoCode* = defined(nimVMDebug) + +when debugEchoCode: + import asciitables when hasFFI: import evalffi @@ -43,9 +48,10 @@ type TGenFlags = set[TGenFlag] proc debugInfo(c: PCtx; info: TLineInfo): string = - result = toFilename(c.config, info).splitFile.name & ":" & $info.line + result = toFileLineCol(c.config, info) proc codeListing(c: PCtx, result: var string, start=0; last = -1) = + ## for debugging purposes # first iteration: compute all necessary labels: var jumpTargets = initIntSet() let last = if last < 0: c.code.len-1 else: min(last, c.code.len-1) @@ -54,7 +60,9 @@ proc codeListing(c: PCtx, result: var string, start=0; last = -1) = if x.opcode in relativeJumps: jumpTargets.incl(i+x.regBx-wordExcess) - # for debugging purposes + template toStr(opc: TOpcode): string = ($opc).substr(3) + + result.add "code listing:\n" var i = start while i <= last: if i in jumpTargets: result.addf("L$1:\n", i) @@ -62,34 +70,39 @@ proc codeListing(c: PCtx, result: var string, start=0; last = -1) = result.add($i) let opc = opcode(x) - if opc in {opcConv, opcCast}: + if opc in {opcIndCall, opcIndCallAsgn}: + result.addf("\t$#\tr$#, r$#, nargs:$#", opc.toStr, x.regA, + x.regB, x.regC) + elif opc in {opcConv, opcCast}: let y = c.code[i+1] let z = c.code[i+2] - result.addf("\t$#\tr$#, r$#, $#, $#", ($opc).substr(3), x.regA, x.regB, + result.addf("\t$#\tr$#, r$#, $#, $#", opc.toStr, x.regA, x.regB, c.types[y.regBx-wordExcess].typeToString, c.types[z.regBx-wordExcess].typeToString) inc i, 2 elif opc < firstABxInstr: - result.addf("\t$#\tr$#, r$#, r$#", ($opc).substr(3), x.regA, + result.addf("\t$#\tr$#, r$#, r$#", opc.toStr, x.regA, x.regB, x.regC) elif opc in relativeJumps: - result.addf("\t$#\tr$#, L$#", ($opc).substr(3), x.regA, + result.addf("\t$#\tr$#, L$#", opc.toStr, x.regA, i+x.regBx-wordExcess) elif opc in {opcLdConst, opcAsgnConst}: let idx = x.regBx-wordExcess - result.addf("\t$#\tr$#, $# ($#)", ($opc).substr(3), x.regA, + result.addf("\t$#\tr$#, $# ($#)", opc.toStr, x.regA, c.constants[idx].renderTree, $idx) elif opc in {opcMarshalLoad, opcMarshalStore}: let y = c.code[i+1] - result.addf("\t$#\tr$#, r$#, $#", ($opc).substr(3), x.regA, x.regB, + result.addf("\t$#\tr$#, r$#, $#", opc.toStr, x.regA, x.regB, c.types[y.regBx-wordExcess].typeToString) inc i else: - result.addf("\t$#\tr$#, $#", ($opc).substr(3), x.regA, x.regBx-wordExcess) + result.addf("\t$#\tr$#, $#", opc.toStr, x.regA, x.regBx-wordExcess) result.add("\t#") result.add(debugInfo(c, c.debug[i])) result.add("\n") inc i + when debugEchoCode: + result = result.alignTable proc echoCode*(c: PCtx; start=0; last = -1) {.deprecated.} = var buf = "" -- cgit 1.4.1-2-gfad0 From 795e5e11ef53260a0a15e25eba36dddbd83900c1 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 15 Jan 2019 12:51:02 -0800 Subject: parseopt2.cmdLineRest is now correct too (#10304) --- lib/pure/parseopt2.nim | 8 ++++---- tests/misc/tparseopt.nim | 35 +++++++++++++++++++---------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/lib/pure/parseopt2.nim b/lib/pure/parseopt2.nim index 51a70b6d1..a84943cf9 100644 --- a/lib/pure/parseopt2.nim +++ b/lib/pure/parseopt2.nim @@ -112,10 +112,10 @@ proc next(p: var OptParser) = p.key = token p.val = "" -proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo2$1", deprecated.} = - ## Returns part of command line string that has not been parsed yet. - ## Do not use - does not correctly handle whitespace. - return p.cmd[p.pos..p.cmd.len-1].join(" ") +proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo2$1".} = + ## Returns the part of command line string that has not been parsed yet, + ## properly quoted. + return p.cmd[p.pos..p.cmd.len-1].quoteShellCommand type GetoptResult* = tuple[kind: CmdLineKind, key, val: TaintedString] diff --git a/tests/misc/tparseopt.nim b/tests/misc/tparseopt.nim index 57253389d..d8824e522 100644 --- a/tests/misc/tparseopt.nim +++ b/tests/misc/tparseopt.nim @@ -97,24 +97,27 @@ else: from stdtest/specialpaths import buildDir import "../.." / compiler/unittest_light - block: # fix #9951 - var p = parseopt.initOptParser(@["echo \"quoted\""]) - let expected = when defined(windows): - """"echo \"quoted\""""" - else: - """'echo "quoted"'""" - assertEquals parseopt.cmdLineRest(p), expected + block: # fix #9951 (and make it work for parseopt and parseopt2) + template runTest(parseoptCustom) = + var p = parseoptCustom.initOptParser(@["echo \"quoted\""]) + let expected = when defined(windows): + """"echo \"quoted\""""" + else: + """'echo "quoted"'""" + assertEquals parseoptCustom.cmdLineRest(p), expected - doAssert "a5'b" == "a5\'b" + doAssert "a5'b" == "a5\'b" - let args = @["a1b", "a2 b", "", "a4\"b", "a5'b", r"a6\b", "a7\'b"] - var p2 = parseopt.initOptParser(args) - let expected2 = when defined(windows): - """a1b "a2 b" "" a4\"b a5'b a6\b a7'b""" - else: - """a1b 'a2 b' '' 'a4"b' 'a5'"'"'b' 'a6\b' 'a7'"'"'b'""" - doAssert "a5'b" == "a5\'b" - assertEquals parseopt.cmdLineRest(p2), expected2 + let args = @["a1b", "a2 b", "", "a4\"b", "a5'b", r"a6\b", "a7\'b"] + var p2 = parseoptCustom.initOptParser(args) + let expected2 = when defined(windows): + """a1b "a2 b" "" a4\"b a5'b a6\b a7'b""" + else: + """a1b 'a2 b' '' 'a4"b' 'a5'"'"'b' 'a6\b' 'a7'"'"'b'""" + doAssert "a5'b" == "a5\'b" + assertEquals parseoptCustom.cmdLineRest(p2), expected2 + runTest(parseopt) + runTest(parseopt2) block: # fix #9842 let exe = buildDir / "D20190112T145450".addFileExt(ExeExt) -- cgit 1.4.1-2-gfad0 From 27e366eaec3389c11889375759e682d70b9cf5b6 Mon Sep 17 00:00:00 2001 From: Araq Date: Tue, 15 Jan 2019 22:47:02 +0100 Subject: fixes #8294 [backport] --- lib/pure/terminal.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index 35dc2483c..2b3c08d0d 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -37,7 +37,7 @@ type var gTerm {.threadvar.}: PTerminal -proc newTerminal(): PTerminal +proc newTerminal(): PTerminal {.gcsafe.} proc getTerminal(): PTerminal {.inline.} = if isNil(gTerm): -- cgit 1.4.1-2-gfad0 From ef769e20b3b53de4a6940ae5cbc6751647e402f7 Mon Sep 17 00:00:00 2001 From: Araq Date: Tue, 15 Jan 2019 22:57:50 +0100 Subject: attempt to fix https://github.com/nim-lang/nightlies/issues/17 --- koch.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/koch.nim b/koch.nim index 5e9537607..b50b6c0e1 100644 --- a/koch.nim +++ b/koch.nim @@ -74,7 +74,8 @@ Web options: build the official docs, use UA-48159761-1 """ -let kochExe* = os.getAppFilename() +let kochExe* = when isMainModule: os.getAppFilename() # always correct when koch is main program, even if `koch` exe renamed eg: `nim c -o:koch_debug koch.nim` + else: getAppDir() / "koch " # `best effort`, works for ur particular use case in nightly proc kochExec*(cmd: string) = exec kochExe.quoteShell & " " & cmd -- cgit 1.4.1-2-gfad0 From 7920dc2898b9518a89c4a588dac2bcdea5658a92 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Tue, 15 Jan 2019 16:58:44 -0500 Subject: Added support for --genDeps on JS backend (#10320) --- compiler/main.nim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/main.nim b/compiler/main.nim index b5f7e8364..49c2666ea 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -92,6 +92,7 @@ proc commandJsonScript(graph: ModuleGraph) = when not defined(leanCompiler): proc commandCompileToJS(graph: ModuleGraph) = + let conf = graph.config #incl(gGlobalOptions, optSafeCode) setTarget(graph.config.target, osJS, cpuJS) #initDefines() @@ -100,6 +101,8 @@ when not defined(leanCompiler): semanticPasses(graph) registerPass(graph, JSgenPass) compileProject(graph) + if optGenScript in graph.config.globalOptions: + writeDepsFile(graph, toGeneratedFile(conf, conf.projectFull, "")) proc interactivePasses(graph: ModuleGraph) = initDefines(graph.config.symbols) -- cgit 1.4.1-2-gfad0 From ece5887ab0af2e95f8327da59e43fda1e4b1b753 Mon Sep 17 00:00:00 2001 From: Araq Date: Tue, 15 Jan 2019 23:07:51 +0100 Subject: another attempt to fix https://github.com/nim-lang/nightlies/issues/17 --- koch.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/koch.nim b/koch.nim index b50b6c0e1..7059e87a5 100644 --- a/koch.nim +++ b/koch.nim @@ -75,7 +75,7 @@ Web options: """ let kochExe* = when isMainModule: os.getAppFilename() # always correct when koch is main program, even if `koch` exe renamed eg: `nim c -o:koch_debug koch.nim` - else: getAppDir() / "koch " # `best effort`, works for ur particular use case in nightly + else: getAppDir() / "koch".exe # works for winrelease proc kochExec*(cmd: string) = exec kochExe.quoteShell & " " & cmd -- cgit 1.4.1-2-gfad0 From 1d7e3a84fd8ebaf183a231c9f8e0159f243ee25a Mon Sep 17 00:00:00 2001 From: Oscar Nihlgård Date: Tue, 15 Jan 2019 23:48:19 +0100 Subject: Improve exception usage in parseutils (#10284) --- changelog.md | 2 ++ lib/pure/parseutils.nim | 56 ++++++++++++++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/changelog.md b/changelog.md index 2ac668cc6..d56d22506 100644 --- a/changelog.md +++ b/changelog.md @@ -36,6 +36,8 @@ - `system.ValueError` now inherits from `system.CatchableError` instead of `system.Defect`. +- The procs `parseutils.parseBiggsetInt`, `parseutils.parseInt`, `parseutils.parseBiggestUInt` and `parseutils.parseUInt` now raise a `ValueError` when the parsed integer is outside of the valid range. Previously they sometimes raised a `OverflowError` and sometimes returned `0`. + - nre's `RegexMatch.{captureBounds,captures}[]` no longer return `Option` or `nil`/`""`, respectivly. Use the newly added `n in p.captures` method to check if a group is captured, otherwise you'll recieve an exception. diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index fb4bc19af..36fcb1965 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -242,8 +242,9 @@ proc captureBetween*(s: string, first: char, second = '\0', start = 0): string = result = "" discard s.parseUntil(result, if second == '\0': first else: second, i) -{.push overflowChecks: on.} -# this must be compiled with overflow checking turned on: +template integerOutOfRangeError(): ref ValueError = + newException(ValueError, "Parsed integer outside of valid range") + proc rawParseInt(s: string, b: var BiggestInt, start = 0): int = var sign: BiggestInt = -1 @@ -256,38 +257,45 @@ proc rawParseInt(s: string, b: var BiggestInt, start = 0): int = if i < s.len and s[i] in {'0'..'9'}: b = 0 while i < s.len and s[i] in {'0'..'9'}: - b = b * 10 - (ord(s[i]) - ord('0')) + let c = ord(s[i]) - ord('0') + if b >= (low(int) + c) div 10: + b = b * 10 - c + else: + raise integerOutOfRangeError() inc(i) while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored + if sign == -1 and b == low(int): + raise integerOutOfRangeError() b = b * sign result = i - start -{.pop.} # overflowChecks proc parseBiggestInt*(s: string, number: var BiggestInt, start = 0): int {. - rtl, extern: "npuParseBiggestInt", noSideEffect.} = + rtl, extern: "npuParseBiggestInt", noSideEffect, raises: [ValueError].} = ## parses an integer starting at `start` and stores the value into `number`. ## Result is the number of processed chars or 0 if there is no integer. - ## `OverflowError` is raised if an overflow occurs. + ## `ValueError` is raised if the parsed integer is out of the valid range. var res: BiggestInt # use 'res' for exception safety (don't write to 'number' in case of an # overflow exception): result = rawParseInt(s, res, start) - number = res + if result != 0: + number = res proc parseInt*(s: string, number: var int, start = 0): int {. - rtl, extern: "npuParseInt", noSideEffect.} = + rtl, extern: "npuParseInt", noSideEffect, raises: [ValueError].} = ## parses an integer starting at `start` and stores the value into `number`. ## Result is the number of processed chars or 0 if there is no integer. - ## `OverflowError` is raised if an overflow occurs. + ## `ValueError` is raised if the parsed integer is out of the valid range. var res: BiggestInt result = parseBiggestInt(s, res, start) - if (sizeof(int) <= 4) and - ((res < low(int)) or (res > high(int))): - raise newException(OverflowError, "overflow") - elif result != 0: + when sizeof(int) <= 4: + if res < low(int) or res > high(int): + raise integerOutOfRangeError() + if result != 0: number = int(res) -proc parseSaturatedNatural*(s: string, b: var int, start = 0): int = +proc parseSaturatedNatural*(s: string, b: var int, start = 0): int {. + raises: [].}= ## parses a natural number into ``b``. This cannot raise an overflow ## error. ``high(int)`` is returned for an overflow. ## The number of processed character is returned. @@ -312,12 +320,13 @@ proc parseSaturatedNatural*(s: string, b: var int, start = 0): int = while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored result = i - start -# overflowChecks doesn't work with BiggestUInt proc rawParseUInt(s: string, b: var BiggestUInt, start = 0): int = var res = 0.BiggestUInt prev = 0.BiggestUInt i = start + if i < s.len - 1 and s[i] == '-' and s[i + 1] in {'0'..'9'}: + raise integerOutOfRangeError() if i < s.len and s[i] == '+': inc(i) # Allow if i < s.len and s[i] in {'0'..'9'}: b = 0 @@ -325,35 +334,34 @@ proc rawParseUInt(s: string, b: var BiggestUInt, start = 0): int = prev = res res = res * 10 + (ord(s[i]) - ord('0')).BiggestUInt if prev > res: - return 0 # overflowChecks emulation + raise integerOutOfRangeError() inc(i) while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored b = res result = i - start proc parseBiggestUInt*(s: string, number: var BiggestUInt, start = 0): int {. - rtl, extern: "npuParseBiggestUInt", noSideEffect.} = + rtl, extern: "npuParseBiggestUInt", noSideEffect, raises: [ValueError].} = ## parses an unsigned integer starting at `start` and stores the value ## into `number`. - ## Result is the number of processed chars or 0 if there is no integer - ## or overflow detected. + ## `ValueError` is raised if the parsed integer is out of the valid range. var res: BiggestUInt # use 'res' for exception safety (don't write to 'number' in case of an # overflow exception): result = rawParseUInt(s, res, start) - number = res + if result != 0: + number = res proc parseUInt*(s: string, number: var uint, start = 0): int {. - rtl, extern: "npuParseUInt", noSideEffect.} = + rtl, extern: "npuParseUInt", noSideEffect, raises: [ValueError].} = ## parses an unsigned integer starting at `start` and stores the value ## into `number`. - ## Result is the number of processed chars or 0 if there is no integer or - ## overflow detected. + ## `ValueError` is raised if the parsed integer is out of the valid range. var res: BiggestUInt result = parseBiggestUInt(s, res, start) when sizeof(BiggestUInt) > sizeof(uint) and sizeof(uint) <= 4: if res > 0xFFFF_FFFF'u64: - raise newException(OverflowError, "overflow") + raise integerOutOfRangeError() if result != 0: number = uint(res) -- cgit 1.4.1-2-gfad0 From fbd6743ea1db89eef3dd89ceb7c0cdcb648c8055 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 15 Jan 2019 15:17:52 -0800 Subject: fix sdl_test test that failed in CI cpp mode (#10314) * fix sdl_test test that failed in CI cpp mode * preserve old code for NimInAction in `nim c` mode --- testament/categories.nim | 4 ++-- tests/niminaction/Chapter8/sdl/sdl_test.nim | 24 ++++++++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/testament/categories.nim b/testament/categories.nim index 54b4272ee..e2b5f0a78 100644 --- a/testament/categories.nim +++ b/testament/categories.nim @@ -352,13 +352,13 @@ proc testNimInAction(r: var TResults, cat: Category, options: string) = "8b5d28e985c0542163927d253a3e4fc9", "783299b98179cc725f9c46b5e3b5381f", "1a2b3fba1187c68d6a9bfa66854f3318", - "80f9c3e594a798225046e8a42e990daf" + "391ff57b38d9ea6f3eeb3fe69ab539d3" ] for i, test in tests: let filename = testsDir / test.addFileExt("nim") let testHash = getMD5(readFile(filename).string) - doAssert testHash == refHashes[i], "Nim in Action test " & filename & " was changed." + doAssert testHash == refHashes[i], "Nim in Action test " & filename & " was changed: " & $(i: i, testHash: testHash, refHash: refHashes[i]) # Run the tests. for testfile in tests: test "tests/" & testfile & ".nim" diff --git a/tests/niminaction/Chapter8/sdl/sdl_test.nim b/tests/niminaction/Chapter8/sdl/sdl_test.nim index a49e08911..1c4d258fb 100644 --- a/tests/niminaction/Chapter8/sdl/sdl_test.nim +++ b/tests/niminaction/Chapter8/sdl/sdl_test.nim @@ -17,12 +17,24 @@ discard pollEvent(nil) renderer.setDrawColor 29, 64, 153, 255 renderer.clear renderer.setDrawColor 255, 255, 255, 255 -var points = [ - (260'i32, 320'i32), - (260'i32, 110'i32), - (360'i32, 320'i32), - (360'i32, 110'i32) -] + +when defined(c): + # just to ensure code from NimInAction still works, but + # the `else` branch would work as well in C mode + var points = [ + (260'i32, 320'i32), + (260'i32, 110'i32), + (360'i32, 320'i32), + (360'i32, 110'i32) + ] +else: + var points = [ + (260.cint, 320.cint), + (260.cint, 110.cint), + (360.cint, 320.cint), + (360.cint, 110.cint) + ] + renderer.drawLines(addr points[0], points.len.cint) renderer.present -- cgit 1.4.1-2-gfad0 From e0afacb9f2c182cf23c2f9dcaa87876b880752d3 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 15 Jan 2019 23:14:55 -0800 Subject: properly fix #10030 by skipping all external configs (#10324) --- build_all.sh | 5 ++--- doc/advopt.txt | 2 +- koch.nim | 7 ++++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build_all.sh b/build_all.sh index 6828bbbd8..0706dcaf2 100644 --- a/build_all.sh +++ b/build_all.sh @@ -27,9 +27,8 @@ build_nim_csources(){ [ -f $nim_csources ] || echo_run build_nim_csources # Note: if fails, may need to `cd csources && git pull` -# Note: --skipUserCfg is to prevent newer flags from -# breaking bootstrap phase -echo_run bin/nim c --skipUserCfg koch +# see D20190115T162028 +echo_run bin/nim c --skipUserCfg --skipParentCfg koch echo_run ./koch boot -d:release echo_run ./koch tools # Compile Nimble and other tools. diff --git a/doc/advopt.txt b/doc/advopt.txt index 2e91deed9..c2e09a3ff 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -77,7 +77,7 @@ Advanced options: --nilseqs:on|off allow 'nil' for strings/seqs for backwards compatibility --oldast:on|off use old AST for backwards compatibility - --skipCfg do not read the general configuration file + --skipCfg do not read the nim installation's configuration file --skipUserCfg do not read the user's configuration file --skipParentCfg do not read the parent dirs' configuration files --skipProjCfg do not read the project's configuration file diff --git a/koch.nim b/koch.nim index 7059e87a5..1d636914d 100644 --- a/koch.nim +++ b/koch.nim @@ -287,9 +287,10 @@ proc boot(args: string) = for i in 0..2: echo "iteration: ", i+1 let extraOption = if i == 0: - "--skipUserCfg" - # forward compatibility: for bootstrap (1st iteration), avoid user flags - # that could break things, see #10030 + "--skipUserCfg --skipParentCfg" + # Note(D20190115T162028:here): the configs are skipped for bootstrap + # (1st iteration) to prevent newer flags from breaking bootstrap phase. + # fixes #10030. else: "" exec i.thVersion & " $# $# $# --nimcache:$# compiler" / "nim.nim" % [bootOptions, extraOption, args, smartNimcache] -- cgit 1.4.1-2-gfad0 From 384e517f09cf63cde136123b163c5c021c992877 Mon Sep 17 00:00:00 2001 From: tweenietomatoes Date: Wed, 16 Jan 2019 11:36:18 +0300 Subject: Most important date change ever (#10323) --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index c333611e2..114086eb6 100644 --- a/readme.md +++ b/readme.md @@ -191,7 +191,7 @@ Nim. You are explicitly permitted to develop commercial applications using Nim. Please read the [copying.txt](copying.txt) file for more details. -Copyright © 2006-2018 Andreas Rumpf, all rights reserved. +Copyright © 2006-2019 Andreas Rumpf, all rights reserved. [nim-site]: https://nim-lang.org [nim-forum]: https://forum.nim-lang.org -- cgit 1.4.1-2-gfad0 From b097081f1049f17b25a3dbfa5746263854cec581 Mon Sep 17 00:00:00 2001 From: narimiran Date: Mon, 7 Jan 2019 18:56:21 +0100 Subject: better docs: sequtils --- lib/pure/collections/sequtils.nim | 717 ++++++++++++++++++++++---------------- tests/manyloc/nake/nakefile.nim | 2 +- 2 files changed, 415 insertions(+), 304 deletions(-) diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 39ba6df49..23ac0902a 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -7,16 +7,73 @@ # distribution, for details about the copyright. # -## :Author: Alexander Mitchell-Robinson (Amrykid) +## :Author: Alexander Mitchell-Robinson (Amrykid) and Nim contributors ## -## This module implements operations for the built-in `seq`:idx: type which -## were inspired by functional programming languages. +## Although this module has ``seq`` in its name, it implements operations +## not only for `seq`:idx: type, but for three built-in container types under +## the ``openArray`` umbrella: +## * sequences +## * strings +## * array ## -## For functional style programming you may want to pass `anonymous procs -## `_ to procs like ``filter`` to -## reduce typing. Anonymous procs can use `the special do notation -## `_ -## which is more convenient in certain situations. +## The system module defines several common functions, such as: +## * ``newseq[T]`` for creating new sequences of type ``T`` +## * ``@`` for converting arrays and strings to sequences +## * ``add`` for adding new elements to strings and sequences +## * ``&`` for string and seq concatenation +## * ``in`` (alias for ``contains``) and ``notin`` for checking if an item is +## in a container +## +## This module builds upon that, providing additional functionality in form of +## procs, iterators and templates inspired by functional programming +## languages. +## +## For functional style programming you have different options at your disposal: +## * pass `anonymous proc`_ +## * import `sugar module`_ and use +## `=> macro.m,untyped,untyped>`_ +## * use `...It templates<#18>`_ +## (`mapIt<#mapIt.t,typed,untyped>`_, +## `filterIt<#filterIt.t,untyped,untyped>`_, etc.) +## +## The chaining of functions is possible thanks to the +## `method call syntax`_. +## +## .. code-block:: +## import sequtils, sugar +## +## # Creating a sequence from 1 to 10, multiplying each member by 2, +## # keeping only the members which are not divisible by 6. +## let +## foo = toSeq(1..10).map(x => x*2).filter(x => x mod 6 != 0) +## bar = toSeq(1..10).mapIt(it*2).filterIt(it mod 6 != 0) +## +## doAssert foo == bar +## echo foo # @[2, 4, 8, 10, 14, 16, 20] +## +## echo foo.any(x => x > 17) # true +## echo bar.allIt(it < 20) # false +## echo foo.foldl(a + b) # 74; sum of all members +## +## .. code-block:: +## import sequtils +## from strutils import join +## +## let +## vowels = @"aeiou" # creates a sequence @['a', 'e', 'i', 'o', 'u'] +## foo = "sequtils is an awesome module" +## +## echo foo.filterIt(it notin vowels).join # "sqtls s n wsm mdl" +## +## ---- +## +## **See also**: +## * `strutils module`_ for common string functions +## * `sugar module`_ for syntactic sugar macros +## * `algorithm module`_ for common generic algorithms +## * `json module`_ for a structure which allows +## heterogeneous members + include "system/inclrtl" @@ -31,7 +88,7 @@ macro evalOnceAs(expAlias, exp: untyped, letAssigneable: static[bool]): untyped ## substitution in macro arguments such as ## https://github.com/nim-lang/Nim/issues/7187 ## ``evalOnceAs(myAlias, myExp)`` will behave as ``let myAlias = myExp`` - ## except when ``letAssigneable`` is false (eg to handle openArray) where + ## except when ``letAssigneable`` is false (e.g. to handle openArray) where ## it just forwards ``exp`` unchanged expectKind(expAlias, nnkIdent) var val = exp @@ -49,16 +106,20 @@ macro evalOnceAs(expAlias, exp: untyped, letAssigneable: static[bool]): untyped proc concat*[T](seqs: varargs[seq[T]]): seq[T] = ## Takes several sequences' items and returns them inside a new sequence. + ## All sequences must be of the same type. ## - ## Example: + ## See also: + ## * `distribute proc<#distribute,seq[T],Positive>`_ for a reverse + ## operation ## - ## .. code-block:: - ## let - ## s1 = @[1, 2, 3] - ## s2 = @[4, 5] - ## s3 = @[6, 7] - ## total = concat(s1, s2, s3) - ## assert total == @[1, 2, 3, 4, 5, 6, 7] + runnableExamples: + let + s1 = @[1, 2, 3] + s2 = @[4, 5] + s3 = @[6, 7] + total = concat(s1, s2, s3) + assert total == @[1, 2, 3, 4, 5, 6, 7] + var L = 0 for seqitm in items(seqs): inc(L, len(seqitm)) newSeq(result, L) @@ -71,13 +132,17 @@ proc concat*[T](seqs: varargs[seq[T]]): seq[T] = proc count*[T](s: openArray[T], x: T): int = ## Returns the number of occurrences of the item `x` in the container `s`. ## - ## Example: - ## - ## .. code-block:: - ## let - ## s = @[1, 2, 2, 3, 2, 4, 2] - ## c = count(s, 2) - ## assert c == 4 + runnableExamples: + let + a = @[1, 2, 2, 3, 2, 4, 2] + b = "abracadabra" + c = count(a, 2) + d = count(a, 99) + e = count(b, 'r') + assert c == 4 + assert d == 0 + assert e == 2 + for itm in items(s): if itm == x: inc result @@ -85,15 +150,14 @@ proc count*[T](s: openArray[T], x: T): int = proc cycle*[T](s: openArray[T], n: Natural): seq[T] = ## Returns a new sequence with the items of the container `s` repeated ## `n` times. + ## `n` must be a non-negative number (zero or more). ## - ## Example: - ## - ## .. code-block:: - ## - ## let - ## s = @[1, 2, 3] - ## total = s.cycle(3) - ## assert total == @[1, 2, 3, 1, 2, 3, 1, 2, 3] + runnableExamples: + let + s = @[1, 2, 3] + total = s.cycle(3) + assert total == @[1, 2, 3, 1, 2, 3, 1, 2, 3] + result = newSeq[T](n * s.len) var o = 0 for x in 0 ..< n: @@ -103,14 +167,13 @@ proc cycle*[T](s: openArray[T], n: Natural): seq[T] = proc repeat*[T](x: T, n: Natural): seq[T] = ## Returns a new sequence with the item `x` repeated `n` times. + ## `n` must be a non-negative number (zero or more). ## - ## Example: - ## - ## .. code-block:: - ## - ## let - ## total = repeat(5, 3) - ## assert total == @[5, 5, 5] + runnableExamples: + let + total = repeat(5, 3) + assert total == @[5, 5, 5] + result = newSeq[T](n) for i in 0 ..< n: result[i] = x @@ -118,16 +181,18 @@ proc repeat*[T](x: T, n: Natural): seq[T] = proc deduplicate*[T](s: openArray[T], isSorted: bool = false): seq[T] = ## Returns a new sequence without duplicates. ## - ## Example: + ## Setting the optional argument ``isSorted`` to ``true`` (default: false) + ## uses a faster algorithm for deduplication. ## - ## .. code-block:: - ## let - ## dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4] - ## dup2 = @["a", "a", "c", "d", "d"] - ## unique1 = deduplicate(dup1) - ## unique2 = deduplicate(dup2) - ## assert unique1 == @[1, 3, 4, 2, 8] - ## assert unique2 == @["a", "c", "d"] + runnableExamples: + let + dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4] + dup2 = @["a", "a", "c", "d", "d"] + unique1 = deduplicate(dup1) + unique2 = deduplicate(dup2, isSorted = true) + assert unique1 == @[1, 3, 4, 2, 8] + assert unique2 == @["a", "c", "d"] + result = @[] if s.len > 0: if isSorted: @@ -144,39 +209,44 @@ proc deduplicate*[T](s: openArray[T], isSorted: bool = false): seq[T] = proc zip*[S, T](s1: openArray[S], s2: openArray[T]): seq[tuple[a: S, b: T]] = ## Returns a new sequence with a combination of the two input containers. ## - ## For convenience you can access the returned tuples through the named - ## fields `a` and `b`. If one container is shorter, the remaining items in - ## the longer container are discarded. + ## The input containers can be of different types. + ## If one container is shorter, the remaining items in the longer container + ## are discarded. ## - ## Example: + ## For convenience you can access the returned tuples through the named + ## fields `a` and `b`. ## - ## .. code-block:: - ## let - ## short = @[1, 2, 3] - ## long = @[6, 5, 4, 3, 2, 1] - ## words = @["one", "two", "three"] - ## zip1 = zip(short, long) - ## zip2 = zip(short, words) - ## assert zip1 == @[(1, 6), (2, 5), (3, 4)] - ## assert zip2 == @[(1, "one"), (2, "two"), (3, "three")] - ## assert zip1[2].b == 4 - ## assert zip2[2].b == "three" + runnableExamples: + let + short = @[1, 2, 3] + long = @[6, 5, 4, 3, 2, 1] + words = @["one", "two", "three"] + letters = "abcd" + zip1 = zip(short, long) + zip2 = zip(short, words) + zip3 = zip(long, letters) + assert zip1 == @[(1, 6), (2, 5), (3, 4)] + assert zip2 == @[(1, "one"), (2, "two"), (3, "three")] + assert zip3 == @[(a: 6, b: 'a'), (a: 5, b: 'b'), (a: 4, b: 'c'), + (a: 3, b: 'd')] + assert zip1[2].b == 4 + assert zip2[2].b == "three" + var m = min(s1.len, s2.len) newSeq(result, m) for i in 0 ..< m: result[i] = (s1[i], s2[i]) proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = - ## Splits and distributes a sequence `s` into `num` sub sequences. + ## Splits and distributes a sequence `s` into `num` sub-sequences. ## - ## Returns a sequence of `num` sequences. For some input values this is the - ## inverse of the `concat <#concat>`_ proc. The proc will assert in debug - ## builds if `s` is nil or `num` is less than one, and will likely crash on - ## release builds. The input sequence `s` can be empty, which will produce + ## Returns a sequence of `num` sequences. For *some* input values this is the + ## inverse of the `concat <#concat,varargs[seq[T]]>`_ proc. + ## The input sequence `s` can be empty, which will produce ## `num` empty sequences. ## ## If `spread` is false and the length of `s` is not a multiple of `num`, the - ## proc will max out the first sub sequences with ``1 + len(s) div num`` + ## proc will max out the first sub-sequence with ``1 + len(s) div num`` ## entries, leaving the remainder of elements to the last sequence. ## ## On the other hand, if `spread` is true, the proc will distribute evenly @@ -184,18 +254,16 @@ proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = ## more suited to multithreading where you are passing equal sized work units ## to a thread pool and want to maximize core usage. ## - ## Example: - ## - ## .. code-block:: - ## let numbers = @[1, 2, 3, 4, 5, 6, 7] - ## assert numbers.distribute(3) == @[@[1, 2, 3], @[4, 5], @[6, 7]] - ## assert numbers.distribute(3, false) == @[@[1, 2, 3], @[4, 5, 6], @[7]] - ## assert numbers.distribute(6)[0] == @[1, 2] - ## assert numbers.distribute(6)[5] == @[7] + runnableExamples: + let numbers = @[1, 2, 3, 4, 5, 6, 7] + assert numbers.distribute(3) == @[@[1, 2, 3], @[4, 5], @[6, 7]] + assert numbers.distribute(3, false) == @[@[1, 2, 3], @[4, 5, 6], @[7]] + assert numbers.distribute(6)[0] == @[1, 2] + assert numbers.distribute(6)[1] == @[3] + if num < 2: result = @[s] return - let num = int(num) # XXX probably only needed because of .. bug # Create the result and calculate the stride size and the remainder if any. @@ -209,13 +277,11 @@ proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = if extra == 0 or spread == false: # Use an algorithm which overcounts the stride and minimizes reading limits. if extra > 0: inc(stride) - for i in 0 ..< num: result[i] = newSeq[T]() for g in first ..< min(s.len, first + stride): result[i].add(s[g]) first += stride - else: # Use an undercounting algorithm which *adds* the remainder each iteration. for i in 0 ..< num: @@ -223,7 +289,6 @@ proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = if extra > 0: extra -= 1 inc(last) - result[i] = newSeq[T]() for g in first ..< last: result[i].add(s[g]) @@ -231,110 +296,103 @@ proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = proc map*[T, S](s: openArray[T], op: proc (x: T): S {.closure.}): seq[S]{.inline.} = - ## Returns a new sequence with the results of `op` applied to every item in - ## the container `s`. + ## Returns a new sequence with the results of `op` proc applied to every + ## item in the container `s`. ## - ## Since the input is not modified you can use this version of ``map`` to + ## Since the input is not modified you can use it to ## transform the type of the elements in the input container. ## - ## Example: + ## See also: + ## * `mapIt template<#mapIt.t,typed,untyped>`_ + ## * `apply proc<#apply,openArray[T],proc(T)_2>`_ for the in-ace version ## - ## .. code-block:: nim - ## let - ## a = @[1, 2, 3, 4] - ## b = map(a, proc(x: int): string = $x) - ## assert b == @["1", "2", "3", "4"] + runnableExamples: + let + a = @[1, 2, 3, 4] + b = map(a, proc(x: int): string = $x) + assert b == @["1", "2", "3", "4"] + newSeq(result, s.len) for i in 0 ..< s.len: result[i] = op(s[i]) -proc map*[T](s: var openArray[T], op: proc (x: var T) {.closure.}) - {.deprecated.} = - ## Applies `op` to every item in `s` modifying it directly. - ## - ## Note that this version of ``map`` requires your input and output types to - ## be the same, since they are modified in-place. - ## - ## Example: - ## - ## .. code-block:: nim - ## var a = @["1", "2", "3", "4"] - ## echo repr(a) - ## # --> ["1", "2", "3", "4"] - ## map(a, proc(x: var string) = x &= "42") - ## echo repr(a) - ## # --> ["142", "242", "342", "442"] - ## **Deprecated since version 0.12.0:** Use the ``apply`` proc instead. - for i in 0 ..< s.len: op(s[i]) - proc apply*[T](s: var openArray[T], op: proc (x: var T) {.closure.}) {.inline.} = ## Applies `op` to every item in `s` modifying it directly. ## - ## Note that this requires your input and output types to - ## be the same, since they are modified in-place. + ## Note that container `s` must be declared as a ``var`` + ## and it is required for your input and output types to + ## be the same, since `s` is modified in-place. ## The parameter function takes a ``var T`` type parameter. ## - ## Example: - ## - ## .. code-block:: nim - ## var a = @["1", "2", "3", "4"] - ## echo repr(a) - ## # --> ["1", "2", "3", "4"] - ## apply(a, proc(x: var string) = x &= "42") - ## echo repr(a) - ## # --> ["142", "242", "342", "442"] + ## See also: + ## * `applyIt template<#applyIt.t,untyped,untyped>`_ + ## * `map proc<#map,openArray[T],proc(T)>`_ ## + runnableExamples: + var a = @["1", "2", "3", "4"] + apply(a, proc(x: var string) = x &= "42") + assert a == @["142", "242", "342", "442"] + for i in 0 ..< s.len: op(s[i]) proc apply*[T](s: var openArray[T], op: proc (x: T): T {.closure.}) {.inline.} = ## Applies `op` to every item in `s` modifying it directly. ## - ## Note that this requires your input and output types to - ## be the same, since they are modified in-place. + ## Note that container `s` must be declared as a ``var`` + ## and it is required for your input and output types to + ## be the same, since `s` is modified in-place. ## The parameter function takes and returns a ``T`` type variable. ## - ## Example: - ## - ## .. code-block:: nim - ## var a = @["1", "2", "3", "4"] - ## echo repr(a) - ## # --> ["1", "2", "3", "4"] - ## apply(a, proc(x: string): string = x & "42") - ## echo repr(a) - ## # --> ["142", "242", "342", "442"] + ## See also: + ## * `applyIt template<#applyIt.t,untyped,untyped>`_ + ## * `map proc<#map,openArray[T],proc(T)>`_ ## + runnableExamples: + var a = @["1", "2", "3", "4"] + apply(a, proc(x: string): string = x & "42") + assert a == @["142", "242", "342", "442"] + for i in 0 ..< s.len: s[i] = op(s[i]) iterator filter*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): T = - ## Iterates through a container and yields every item that fulfills the - ## predicate. + ## Iterates through a container `s` and yields every item that fulfills the + ## predicate `pred` (function that returns a `bool`). ## - ## Example: + ## See also: + ## * `fliter proc<#filter,openArray[T],proc(T)>`_ + ## * `filterIt template<#filterIt.t,untyped,untyped>`_ ## - ## .. code-block:: - ## let numbers = @[1, 4, 5, 8, 9, 7, 4] - ## for n in filter(numbers, proc (x: int): bool = x mod 2 == 0): - ## echo($n) - ## # echoes 4, 8, 4 in separate lines + runnableExamples: + let numbers = @[1, 4, 5, 8, 9, 7, 4] + var evens = newSeq[int]() + for n in filter(numbers, proc (x: int): bool = x mod 2 == 0): + evens.add(n) + assert evens == @[4, 8, 4] + for i in 0 ..< s.len: if pred(s[i]): yield s[i] proc filter*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): seq[T] {.inline.} = - ## Returns a new sequence with all the items that fulfilled the predicate. + ## Returns a new sequence with all the items of `s` that fulfilled the + ## predicate `pred` (function that returns a `bool`). ## - ## Example: + ## See also: + ## * `filterIt template<#filterIt.t,untyped,untyped>`_ + ## * `filter iterator<#filter.i,openArray[T],proc(T)>`_ + ## * `keepIf proc<#keepIf,seq[T],proc(T)>`_ for the in-ace version ## - ## .. code-block:: - ## let - ## colors = @["red", "yellow", "black"] - ## f1 = filter(colors, proc(x: string): bool = x.len < 6) - ## f2 = filter(colors) do (x: string) -> bool : x.len > 5 - ## assert f1 == @["red", "black"] - ## assert f2 == @["yellow"] + runnableExamples: + let + colors = @["red", "yellow", "black"] + f1 = filter(colors, proc(x: string): bool = x.len < 6) + f2 = filter(colors, proc(x: string): bool = x.contains('y')) + assert f1 == @["red", "black"] + assert f2 == @["yellow"] + result = newSeq[T]() for i in 0 ..< s.len: if pred(s[i]): @@ -342,15 +400,23 @@ proc filter*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): seq[T] proc keepIf*[T](s: var seq[T], pred: proc(x: T): bool {.closure.}) {.inline.} = - ## Keeps the items in the passed sequence if they fulfilled the predicate. - ## Same as the ``filter`` proc, but modifies the sequence directly. + ## Keeps the items in the passed sequence `s` if they fulfilled the + ## predicate `pred` (function that returns a `bool`). ## - ## Example: + ## Note that `s` must be declared as a ``var``. ## - ## .. code-block:: - ## var floats = @[13.0, 12.5, 5.8, 2.0, 6.1, 9.9, 10.1] - ## keepIf(floats, proc(x: float): bool = x > 10) - ## assert floats == @[13.0, 12.5, 10.1] + ## Similar to the `filter proc<#filter,openArray[T],proc(T)>`_, + ## but modifies the sequence directly. + ## + ## See also: + ## * `keepItIf template<#keepItIf.t,seq,untyped>`_ + ## * `filter proc<#filter,openArray[T],proc(T)>`_ + ## + runnableExamples: + var floats = @[13.0, 12.5, 5.8, 2.0, 6.1, 9.9, 10.1] + keepIf(floats, proc(x: float): bool = x > 10) + assert floats == @[13.0, 12.5, 10.1] + var pos = 0 for i in 0 ..< len(s): if pred(s[i]): @@ -360,16 +426,15 @@ proc keepIf*[T](s: var seq[T], pred: proc(x: T): bool {.closure.}) setLen(s, pos) proc delete*[T](s: var seq[T]; first, last: Natural) = - ## Deletes in `s` the items at position `first` .. `last`. This modifies - ## `s` itself, it does not return a copy. - ## - ## Example: + ## Deletes in the items of a sequence `s` at positions ``first..last`` + ## (including both ends of a range). + ## This modifies `s` itself, it does not return a copy. ## - ##.. code-block:: - ## let outcome = @[1,1,1,1,1,1,1,1] - ## var dest = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1] - ## dest.delete(3, 8) - ## assert outcome == dest + runnableExamples: + let outcome = @[1,1,1,1,1,1,1,1] + var dest = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1] + dest.delete(3, 8) + assert outcome == dest var i = first var j = last+1 @@ -384,15 +449,15 @@ proc insert*[T](dest: var seq[T], src: openArray[T], pos=0) = ## Inserts items from `src` into `dest` at position `pos`. This modifies ## `dest` itself, it does not return a copy. ## - ## Example: + ## Notice that `src` and `dest` must be of the same type. ## - ##.. code-block:: - ## var dest = @[1,1,1,1,1,1,1,1] - ## let - ## src = @[2,2,2,2,2,2] - ## outcome = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1] - ## dest.insert(src, 3) - ## assert dest == outcome + runnableExamples: + var dest = @[1,1,1,1,1,1,1,1] + let + src = @[2,2,2,2,2,2] + outcome = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1] + dest.insert(src, 3) + assert dest == outcome var j = len(dest) - 1 var i = len(dest) + len(src) - 1 @@ -411,37 +476,48 @@ proc insert*[T](dest: var seq[T], src: openArray[T], pos=0) = template filterIt*(s, pred: untyped): untyped = - ## Returns a new sequence with all the items that fulfilled the predicate. + ## Returns a new sequence with all the items of `s` that fulfilled the + ## predicate `pred`. ## - ## Unlike the `proc` version, the predicate needs to be an expression using - ## the ``it`` variable for testing, like: ``filterIt("abcxyz", it == 'x')``. + ## Unlike the `filter proc<#filter,openArray[T],proc(T)>`_ and + ## `filter iterator<#filter.i,openArray[T],proc(T)>`_, + ## the predicate needs to be an expression using the ``it`` variable + ## for testing, like: ``filterIt("abcxyz", it == 'x')``. ## - ## Example: + ## See also: + ## * `fliter proc<#filter,openArray[T],proc(T)>`_ + ## * `filter iterator<#filter.i,openArray[T],proc(T)>`_ ## - ## .. code-block:: - ## let - ## temperatures = @[-272.15, -2.0, 24.5, 44.31, 99.9, -113.44] - ## acceptable = filterIt(temperatures, it < 50 and it > -10) - ## notAcceptable = filterIt(temperatures, it > 50 or it < -10) - ## assert acceptable == @[-2.0, 24.5, 44.31] - ## assert notAcceptable == @[-272.15, 99.9, -113.44] + runnableExamples: + let + temperatures = @[-272.15, -2.0, 24.5, 44.31, 99.9, -113.44] + acceptable = temperatures.filterIt(it < 50 and it > -10) + notAcceptable = temperatures.filterIt(it > 50 or it < -10) + assert acceptable == @[-2.0, 24.5, 44.31] + assert notAcceptable == @[-272.15, 99.9, -113.44] + var result = newSeq[type(s[0])]() for it {.inject.} in items(s): if pred: result.add(it) result template keepItIf*(varSeq: seq, pred: untyped) = - ## Convenience template around the ``keepIf`` proc to reduce typing. + ## Keeps the items in the passed sequence (must be declared as a ``var``) + ## if they fulfilled the predicate. ## - ## Unlike the `proc` version, the predicate needs to be an expression using + ## Unlike the `keepIf proc<#keepIf,seq[T],proc(T)>`_, + ## the predicate needs to be an expression using ## the ``it`` variable for testing, like: ``keepItIf("abcxyz", it == 'x')``. ## - ## Example: + ## See also: + ## * `keepIf proc<#keepIf,seq[T],proc(T)>`_ + ## * `filterIt template<#filterIt.t,untyped,untyped>`_ ## - ## .. code-block:: - ## var candidates = @["foo", "bar", "baz", "foobar"] - ## keepItIf(candidates, it.len == 3 and it[0] == 'b') - ## assert candidates == @["bar", "baz"] + runnableExamples: + var candidates = @["foo", "bar", "baz", "foobar"] + candidates.keepItIf(it.len == 3 and it[0] == 'b') + assert candidates == @["bar", "baz"] + var pos = 0 for i in 0 ..< len(varSeq): let it {.inject.} = varSeq[i] @@ -455,26 +531,37 @@ proc all*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): bool = ## Iterates through a container and checks if every item fulfills the ## predicate. ## - ## Example: + ## See also: + ## * `allIt template<#allIt.t,untyped,untyped>`_ + ## * `any proc<#any,openArray[T],proc(T)>`_ ## - ## .. code-block:: - ## let numbers = @[1, 4, 5, 8, 9, 7, 4] - ## assert all(numbers, proc (x: int): bool = return x < 10) == true - ## assert all(numbers, proc (x: int): bool = return x < 9) == false + runnableExamples: + let numbers = @[1, 4, 5, 8, 9, 7, 4] + assert all(numbers, proc (x: int): bool = return x < 10) == true + assert all(numbers, proc (x: int): bool = return x < 9) == false + for i in s: if not pred(i): return false return true template allIt*(s, pred: untyped): bool = - ## Checks if every item fulfills the predicate. + ## Iterates through a container and checks if every item fulfills the + ## predicate. ## - ## Example: + ## Unlike the `all proc<#all,openArray[T],proc(T)>`_, + ## the predicate needs to be an expression using + ## the ``it`` variable for testing, like: ``allIt("abba", it == 'a')``. ## - ## .. code-block:: - ## let numbers = @[1, 4, 5, 8, 9, 7, 4] - ## assert allIt(numbers, it < 10) == true - ## assert allIt(numbers, it < 9) == false + ## See also: + ## * `all proc<#all,openArray[T],proc(T)>`_ + ## * `anyIt template<#anyIt.t,untyped,untyped>`_ + ## + runnableExamples: + let numbers = @[1, 4, 5, 8, 9, 7, 4] + assert numbers.allIt(it < 10) == true + assert numbers.allIt(it < 9) == false + var result = true for it {.inject.} in items(s): if not pred: @@ -486,26 +573,37 @@ proc any*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): bool = ## Iterates through a container and checks if some item fulfills the ## predicate. ## - ## Example: + ## See also: + ## * `anyIt template<#anyIt.t,untyped,untyped>`_ + ## * `all proc<#all,openArray[T],proc(T)>`_ ## - ## .. code-block:: - ## let numbers = @[1, 4, 5, 8, 9, 7, 4] - ## assert any(numbers, proc (x: int): bool = return x > 8) == true - ## assert any(numbers, proc (x: int): bool = return x > 9) == false + runnableExamples: + let numbers = @[1, 4, 5, 8, 9, 7, 4] + assert any(numbers, proc (x: int): bool = return x > 8) == true + assert any(numbers, proc (x: int): bool = return x > 9) == false + for i in s: if pred(i): return true return false template anyIt*(s, pred: untyped): bool = - ## Checks if some item fulfills the predicate. + ## Iterates through a container and checks if some item fulfills the + ## predicate. ## - ## Example: + ## Unlike the `any proc<#any,openArray[T],proc(T)>`_, + ## the predicate needs to be an expression using + ## the ``it`` variable for testing, like: ``anyIt("abba", it == 'a')``. ## - ## .. code-block:: - ## let numbers = @[1, 4, 5, 8, 9, 7, 4] - ## assert anyIt(numbers, it > 8) == true - ## assert anyIt(numbers, it > 9) == false + ## See also: + ## * `any proc<#any,openArray[T],proc(T)>`_ + ## * `allIt template<#allIt.t,untyped,untyped>`_ + ## + runnableExamples: + let numbers = @[1, 4, 5, 8, 9, 7, 4] + assert numbers.anyIt(it > 8) == true + assert numbers.anyIt(it > 9) == false + var result = false for it {.inject.} in items(s): if pred: @@ -555,19 +653,28 @@ template toSeq2(iter: iterator): untyped = result template toSeq*(iter: untyped): untyped = - ## Transforms any iterable into a sequence. + ## Transforms any iterable (anything that can be iterated over, e.g. with + ## a for-loop) into a sequence. + ## runnableExamples: let - numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9] - odd_numbers = toSeq(filter(numeric, proc(x: int): bool = x mod 2 == 1)) - doAssert odd_numbers == @[1, 3, 5, 7, 9] + myRange = 1..5 + mySet: set[int8] = {5'i8, 3, 1} + assert type(myRange) is HSlice[system.int, system.int] + assert type(mySet) is set[int8] + + let + mySeq1 = toSeq(myRange) + mySeq2 = toSeq(mySet) + assert mySeq1 == @[1, 2, 3, 4, 5] + assert mySeq2 == @[1'i8, 3, 5] when compiles(toSeq1(iter)): toSeq1(iter) elif compiles(toSeq2(iter)): toSeq2(iter) else: - # overload for untyped, eg: `toSeq(myInlineIterator(3))` + # overload for untyped, e.g.: `toSeq(myInlineIterator(3))` when compiles(iter.len): block: evalOnceAs(iter2, iter, true) @@ -597,20 +704,23 @@ template foldl*(sequence, operation: untyped): untyped = ## the sequence of numbers 1, 2 and 3 will be parenthesized as (((1) - 2) - ## 3). ## - ## Example: + ## See also: + ## * `foldl template<#foldl.t,,,>`_ with a starting parameter + ## * `foldr template<#foldr.t,untyped,untyped>`_ ## - ## .. code-block:: - ## let - ## numbers = @[5, 9, 11] - ## addition = foldl(numbers, a + b) - ## subtraction = foldl(numbers, a - b) - ## multiplication = foldl(numbers, a * b) - ## words = @["nim", "is", "cool"] - ## concatenation = foldl(words, a & b) - ## assert addition == 25, "Addition is (((5)+9)+11)" - ## assert subtraction == -15, "Subtraction is (((5)-9)-11)" - ## assert multiplication == 495, "Multiplication is (((5)*9)*11)" - ## assert concatenation == "nimiscool" + runnableExamples: + let + numbers = @[5, 9, 11] + addition = foldl(numbers, a + b) + subtraction = foldl(numbers, a - b) + multiplication = foldl(numbers, a * b) + words = @["nim", "is", "cool"] + concatenation = foldl(words, a & b) + assert addition == 25, "Addition is (((5)+9)+11)" + assert subtraction == -15, "Subtraction is (((5)-9)-11)" + assert multiplication == 495, "Multiplication is (((5)*9)*11)" + assert concatenation == "nimiscool" + let s = sequence assert s.len > 0, "Can't fold empty sequences" var result: type(s[0]) @@ -625,20 +735,22 @@ template foldl*(sequence, operation: untyped): untyped = template foldl*(sequence, operation, first): untyped = ## Template to fold a sequence from left to right, returning the accumulation. ## - ## This version of ``foldl`` gets a starting parameter. This makes it possible + ## This version of ``foldl`` gets a **starting parameter**. This makes it possible ## to accumulate the sequence into a different type than the sequence elements. ## ## The ``operation`` parameter should be an expression which uses the variables ## ``a`` and ``b`` for each step of the fold. The ``first`` parameter is the ## start value (the first ``a``) and therefor defines the type of the result. ## - ## Example: + ## See also: + ## * `foldr template<#foldr.t,untyped,untyped>`_ ## - ## .. code-block:: - ## let - ## numbers = @[0, 8, 1, 5] - ## digits = foldl(numbers, a & (chr(b + ord('0'))), "") - ## assert digits == "0815" + runnableExamples: + let + numbers = @[0, 8, 1, 5] + digits = foldl(numbers, a & (chr(b + ord('0'))), "") + assert digits == "0815" + var result: type(first) result = first for x in items(sequence): @@ -662,20 +774,23 @@ template foldr*(sequence, operation: untyped): untyped = ## the sequence of numbers 1, 2 and 3 will be parenthesized as (1 - (2 - ## (3))). ## - ## Example: + ## See also: + ## * `foldl template<#foldl.t,untyped,untyped>`_ + ## * `foldl template<#foldl.t,,,>`_ with a starting parameter ## - ## .. code-block:: - ## let - ## numbers = @[5, 9, 11] - ## addition = foldr(numbers, a + b) - ## subtraction = foldr(numbers, a - b) - ## multiplication = foldr(numbers, a * b) - ## words = @["nim", "is", "cool"] - ## concatenation = foldr(words, a & b) - ## assert addition == 25, "Addition is (5+(9+(11)))" - ## assert subtraction == 7, "Subtraction is (5-(9-(11)))" - ## assert multiplication == 495, "Multiplication is (5*(9*(11)))" - ## assert concatenation == "nimiscool" + runnableExamples: + let + numbers = @[5, 9, 11] + addition = foldr(numbers, a + b) + subtraction = foldr(numbers, a - b) + multiplication = foldr(numbers, a * b) + words = @["nim", "is", "cool"] + concatenation = foldr(words, a & b) + assert addition == 25, "Addition is (5+(9+(11)))" + assert subtraction == 7, "Subtraction is (5-(9-(11)))" + assert multiplication == 495, "Multiplication is (5*(9*(11)))" + assert concatenation == "nimiscool" + let s = sequence assert s.len > 0, "Can't fold empty sequences" var result: type(s[0]) @@ -687,41 +802,26 @@ template foldr*(sequence, operation: untyped): untyped = result = operation result -template mapIt*(s, typ, op: untyped): untyped = - ## Convenience template around the ``map`` proc to reduce typing. - ## - ## The template injects the ``it`` variable which you can use directly in an - ## expression. You also need to pass as `typ` the type of the expression, - ## since the new returned sequence can have a different type than the - ## original. - ## - ## Example: - ## - ## .. code-block:: - ## let - ## nums = @[1, 2, 3, 4] - ## strings = nums.mapIt(string, $(4 * it)) - ## assert strings == @["4", "8", "12", "16"] - ## **Deprecated since version 0.12.0:** Use the ``mapIt(seq1, op)`` - ## template instead. - var result: seq[typ] = @[] - for it {.inject.} in items(s): - result.add(op) - result - template mapIt*(s: typed, op: untyped): untyped = - ## Convenience template around the ``map`` proc to reduce typing. + ## Returns a new sequence with the results of `op` proc applied to every + ## item in the container `s`. + ## + ## Since the input is not modified you can use it to + ## transform the type of the elements in the input container. ## ## The template injects the ``it`` variable which you can use directly in an ## expression. ## - ## Example: + ## See also: + ## * `map proc<#map,openArray[T],proc(T)>`_ + ## * `applyIt template<#applyIt.t,untyped,untyped>`_ for the in-ace version ## - ## .. code-block:: - ## let - ## nums = @[1, 2, 3, 4] - ## strings = nums.mapIt($(4 * it)) - ## assert strings == @["4", "8", "12", "16"] + runnableExamples: + let + nums = @[1, 2, 3, 4] + strings = nums.mapIt($(4 * it)) + assert strings == @["4", "8", "12", "16"] + when defined(nimHasTypeof): type outType = typeof(( block: @@ -758,31 +858,38 @@ template applyIt*(varSeq, op: untyped) = ## expression. The expression has to return the same type as the sequence you ## are mutating. ## - ## Example: + ## See also: + ## * `apply proc<#apply,openArray[T],proc(T)_2>`_ + ## * `mapIt template<#mapIt.t,typed,untyped>`_ ## - ## .. code-block:: - ## var nums = @[1, 2, 3, 4] - ## nums.applyIt(it * 3) - ## assert nums[0] + nums[3] == 15 + runnableExamples: + var nums = @[1, 2, 3, 4] + nums.applyIt(it * 3) + assert nums[0] + nums[3] == 15 + for i in low(varSeq) .. high(varSeq): let it {.inject.} = varSeq[i] varSeq[i] = op template newSeqWith*(len: int, init: untyped): untyped = - ## creates a new sequence, calling `init` to initialize each value. + ## Creates a new sequence of length `len`, calling `init` to initialize + ## each value of the sequence. ## - ## Example: + ## Useful for creating "2D" sequences - sequences containing other sequences + ## or to populate fields of the created sequence. ## - ## .. code-block:: - ## var seq2D = newSeqWith(20, newSeq[bool](10)) - ## seq2D[0][0] = true - ## seq2D[1][0] = true - ## seq2D[0][1] = true - ## - ## import random - ## var seqRand = newSeqWith(20, random(10)) - ## echo seqRand + runnableExamples: + ## Creates a seqence containing 5 bool sequences, each of length of 3. + var seq2D = newSeqWith(5, newSeq[bool](3)) + assert seq2D.len == 5 + assert seq2D[0].len == 3 + assert seq2D[4][2] == false + + ## Creates a sequence of 20 random numbers from 1 to 10 + import random + var seqRand = newSeqWith(20, random(10)) + var result = newSeq[type(init)](len) for i in 0 ..< len: result[i] = init @@ -804,7 +911,7 @@ proc mapLitsImpl(constructor: NimNode; op: NimNode; nested: bool; macro mapLiterals*(constructor, op: untyped; nested = true): untyped = - ## applies ``op`` to each of the **atomic** literals like ``3`` + ## Applies ``op`` to each of the **atomic** literals like ``3`` ## or ``"abc"`` in the specified ``constructor`` AST. This can ## be used to map every array element to some target type: ## @@ -819,16 +926,20 @@ macro mapLiterals*(constructor, op: untyped; ## .. code-block:: ## let x = [int(0.1), int(1.2), int(2.3), int(3.4)] ## - ## If ``nested`` is true, the literals are replaced everywhere - ## in the ``constructor`` AST, otherwise only the first level + ## If ``nested`` is true (which is the default), the literals are replaced + ## everywhere in the ``constructor`` AST, otherwise only the first level ## is considered: ## ## .. code-block:: - ## mapLiterals((1, ("abc"), 2), float, nested=false) - ## - ## Produces:: - ## - ## (float(1), ("abc"), float(2)) + ## let a = mapLiterals((1.2, (2.3, 3.4), 4.8), int) + ## let b = mapLiterals((1.2, (2.3, 3.4), 4.8), int, nested=false) + ## assert a == (1, (2, 3), 4) + ## assert b == (1, (2.3, 3.4), 4) + ## + ## let c = mapLiterals((1, (2, 3), 4, (5, 6)), `$`) + ## let d = mapLiterals((1, (2, 3), 4, (5, 6)), `$`, nested=false) + ## assert c == ("1", ("2", "3"), "4", ("5", "6")) + ## assert d == ("1", (2, 3), "4", (5, 6)) ## ## There are no constraints for the ``constructor`` AST, it ## works for nested tuples of arrays of sets etc. diff --git a/tests/manyloc/nake/nakefile.nim b/tests/manyloc/nake/nakefile.nim index 3e8609169..9c66ad71c 100644 --- a/tests/manyloc/nake/nakefile.nim +++ b/tests/manyloc/nake/nakefile.nim @@ -76,7 +76,7 @@ task "testskel", "create skeleton test dir for testing": task "clean", "cleanup generated files": var dirs = @["nimcache", "server"/"nimcache"] - dirs.map(proc(x: var string) = + dirs.apply(proc(x: var string) = if existsDir(x): removeDir(x)) task "download", "download game assets": -- cgit 1.4.1-2-gfad0 From be6456f0f41a06ff2ed05c76cbaff4b087cd8b31 Mon Sep 17 00:00:00 2001 From: narimiran Date: Wed, 9 Jan 2019 16:26:18 +0100 Subject: better docs: strutils --- lib/pure/strutils.nim | 2041 +++++++++++++++++----------- tests/errmsgs/tunknown_named_parameter.nim | 8 +- 2 files changed, 1273 insertions(+), 776 deletions(-) diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 00469f9e5..4bb640094 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -7,11 +7,72 @@ # distribution, for details about the copyright. # -## This module contains various string utility routines. -## See the module `re `_ for regular expression support. -## See the module `pegs `_ for PEG support. +## :Author: Nim contributors +## +## The system module defines several common functions for working with strings, +## such as: +## * ``$`` for converting other data-types to strings +## * ``&`` for string concatenation +## * ``add`` for adding a new character or a string to the existing one +## * ``in`` (alias for ``contains``) and ``notin`` for checking if a character +## is in a string +## +## This module builds upon that, providing additional functionality in form of +## procedures, iterators and templates for strings. +## +## .. code-block:: +## import strutils +## +## let +## numbers = @[867, 5309] +## multiLineString = "first line\nsecond line\nthird line" +## +## let jenny = numbers.join("-") +## assert jenny == "867-5309" +## +## assert splitLines(multiLineString) == +## @["first line", "second line", "third line"] +## assert split(multiLineString) == @["first", "line", "second", +## "line", "third", "line"] +## assert indent(multiLineString, 4) == +## " first line\n second line\n third line" +## assert 'z'.repeat(5) == "zzzzz" +## +## The chaining of functions is possible thanks to the +## `method call syntax`_: +## +## .. code-block:: +## import strutils +## from sequtils import map +## +## let jenny = "867-5309" +## assert jenny.split('-').map(parseInt) == @[867, 5309] +## +## assert "Beetlejuice".indent(1).repeat(3).strip == +## "Beetlejuice Beetlejuice Beetlejuice" +## ## This module is available for the `JavaScript target ## `_. +## +## ---- +## +## **See also:** +## * `strformat module`_ for string interpolation and formatting +## * `unicode module`_ for Unicode UTF-8 handling +## * `sequtils module`_ for operations on container +## types (including strings) +## * `parseutils module`_ for lower-level parsing of tokens, +## numbers, identifiers, etc. +## * `parseopt module`_ for command-line parsing +## * `strtabs module`_ for efficient hash tables +## (dictionaries, in some programming languages) mapping from strings to strings +## * `pegs module`_ for PEG (Parsing Expression Grammar) support +## * `ropes module`_ for rope data type, which can represent very +## long strings efficiently +## * `re module`_ for regular expression (regex) support +## * `strscans`_ for ``scanf`` and ``scanp`` macros, which offer +## easier substring extraction than regular expressions + import parseutils from math import pow, floor, log10 @@ -38,7 +99,8 @@ else: const Whitespace* = {' ', '\t', '\v', '\r', '\l', '\f'} - ## All the characters that count as whitespace. + ## All the characters that count as whitespace (space, tab, vertical tab, + ## carriage return, new line, form feed) Letters* = {'A'..'Z', 'a'..'z'} ## the set of letters @@ -56,14 +118,15 @@ const ## the set of characters an identifier can start with NewLines* = {'\13', '\10'} - ## the set of characters a newline terminator can start with + ## the set of characters a newline terminator can start with (carriage + ## return, line feed) AllChars* = {'\x00'..'\xFF'} ## A set with all the possible characters. ## ## Not very useful by its own, you can use it to create *inverted* sets to - ## make the `find() proc <#find,string,set[char],int>`_ find **invalid** - ## characters in strings. Example: + ## make the `find proc<#find,string,set[char],Natural,int>`_ + ## find **invalid** characters in strings. Example: ## ## .. code-block:: nim ## let invalid = AllChars - Digits @@ -72,9 +135,10 @@ const proc isAlphaAscii*(c: char): bool {.noSideEffect, procvar, rtl, extern: "nsuIsAlphaAsciiChar".}= - ## Checks whether or not `c` is alphabetical. + ## Checks whether or not character `c` is alphabetical. ## ## This checks a-z, A-Z ASCII characters only. + ## Use `Unicode module`_ for UTF-8 support. runnableExamples: doAssert isAlphaAscii('e') == true doAssert isAlphaAscii('E') == true @@ -108,6 +172,7 @@ proc isSpaceAscii*(c: char): bool {.noSideEffect, procvar, runnableExamples: doAssert isSpaceAscii('n') == false doAssert isSpaceAscii(' ') == true + doAssert isSpaceAscii('\t') == true return c in Whitespace proc isLowerAscii*(c: char): bool {.noSideEffect, procvar, @@ -115,6 +180,10 @@ proc isLowerAscii*(c: char): bool {.noSideEffect, procvar, ## Checks whether or not `c` is a lower case character. ## ## This checks ASCII characters only. + ## Use `Unicode module`_ for UTF-8 support. + ## + ## See also: + ## * `toLowerAscii proc<#toLowerAscii,char>`_ runnableExamples: doAssert isLowerAscii('e') == true doAssert isLowerAscii('E') == false @@ -126,138 +195,28 @@ proc isUpperAscii*(c: char): bool {.noSideEffect, procvar, ## Checks whether or not `c` is an upper case character. ## ## This checks ASCII characters only. + ## Use `Unicode module`_ for UTF-8 support. + ## + ## See also: + ## * `toUpperAscii proc<#toUpperAscii,char>`_ runnableExamples: doAssert isUpperAscii('e') == false doAssert isUpperAscii('E') == true doAssert isUpperAscii('7') == false return c in {'A'..'Z'} -template isImpl(call) = - if s.len == 0: return false - result = true - for c in s: - if not call(c): return false - -proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsAlphaAsciiStr", - deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = - ## Checks whether or not `s` is alphabetical. - ## - ## This checks a-z, A-Z ASCII characters only. - ## Returns true if all characters in `s` are - ## alphabetic and there is at least one character - ## in `s`. - runnableExamples: - doAssert isAlphaAscii("fooBar") == true - doAssert isAlphaAscii("fooBar1") == false - doAssert isAlphaAscii("foo Bar") == false - isImpl isAlphaAscii - -proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsAlphaNumericStr", - deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = - ## Checks whether or not `s` is alphanumeric. - ## - ## This checks a-z, A-Z, 0-9 ASCII characters only. - ## Returns true if all characters in `s` are - ## alpanumeric and there is at least one character - ## in `s`. - runnableExamples: - doAssert isAlphaNumeric("fooBar") == true - doAssert isAlphaNumeric("fooBar") == true - doAssert isAlphaNumeric("foo Bar") == false - isImpl isAlphaNumeric - -proc isDigit*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsDigitStr", - deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = - ## Checks whether or not `s` is a numeric value. - ## - ## This checks 0-9 ASCII characters only. - ## Returns true if all characters in `s` are - ## numeric and there is at least one character - ## in `s`. - runnableExamples: - doAssert isDigit("1908") == true - doAssert isDigit("fooBar1") == false - isImpl isDigit - -proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsSpaceAsciiStr", - deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = - ## Checks whether or not `s` is completely whitespace. - ## - ## Returns true if all characters in `s` are whitespace - ## characters and there is at least one character in `s`. - runnableExamples: - doAssert isSpaceAscii(" ") == true - doAssert isSpaceAscii("") == false - isImpl isSpaceAscii - -template isCaseImpl(s, charProc, skipNonAlpha) = - var hasAtleastOneAlphaChar = false - if s.len == 0: return false - for c in s: - if skipNonAlpha: - var charIsAlpha = c.isAlphaAscii() - if not hasAtleastOneAlphaChar: - hasAtleastOneAlphaChar = charIsAlpha - if charIsAlpha and (not charProc(c)): - return false - else: - if not charProc(c): - return false - return if skipNonAlpha: hasAtleastOneAlphaChar else: true - -proc isLowerAscii*(s: string, skipNonAlpha: bool): bool {. - deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = - ## Checks whether ``s`` is lower case. - ## - ## This checks ASCII characters only. - ## - ## If ``skipNonAlpha`` is true, returns true if all alphabetical - ## characters in ``s`` are lower case. Returns false if none of the - ## characters in ``s`` are alphabetical. - ## - ## If ``skipNonAlpha`` is false, returns true only if all characters - ## in ``s`` are alphabetical and lower case. - ## - ## For either value of ``skipNonAlpha``, returns false if ``s`` is - ## an empty string. - runnableExamples: - doAssert isLowerAscii("1foobar", false) == false - doAssert isLowerAscii("1foobar", true) == true - doAssert isLowerAscii("1fooBar", true) == false - isCaseImpl(s, isLowerAscii, skipNonAlpha) - -proc isUpperAscii*(s: string, skipNonAlpha: bool): bool {. - deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = - ## Checks whether ``s`` is upper case. - ## - ## This checks ASCII characters only. - ## - ## If ``skipNonAlpha`` is true, returns true if all alphabetical - ## characters in ``s`` are upper case. Returns false if none of the - ## characters in ``s`` are alphabetical. - ## - ## If ``skipNonAlpha`` is false, returns true only if all characters - ## in ``s`` are alphabetical and upper case. - ## - ## For either value of ``skipNonAlpha``, returns false if ``s`` is - ## an empty string. - runnableExamples: - doAssert isUpperAscii("1FOO", false) == false - doAssert isUpperAscii("1FOO", true) == true - doAssert isUpperAscii("1Foo", true) == false - isCaseImpl(s, isUpperAscii, skipNonAlpha) proc toLowerAscii*(c: char): char {.noSideEffect, procvar, rtl, extern: "nsuToLowerAsciiChar".} = - ## Returns the lower case version of ``c``. + ## Returns the lower case version of character ``c``. ## ## This works only for the letters ``A-Z``. See `unicode.toLower ## `_ for a version that works for any Unicode ## character. + ## + ## See also: + ## * `isLowerAscii proc<#isLowerAscii,char>`_ + ## * `toLowerAscii proc<#toLowerAscii,string>`_ for converting a string runnableExamples: doAssert toLowerAscii('A') == 'a' doAssert toLowerAscii('e') == 'e' @@ -273,22 +232,30 @@ template toImpl(call) = proc toLowerAscii*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuToLowerAsciiStr".} = - ## Converts `s` into lower case. + ## Converts string `s` into lower case. ## ## This works only for the letters ``A-Z``. See `unicode.toLower ## `_ for a version that works for any Unicode ## character. + ## + ## See also: + ## * `normalize proc<#normalize,string>`_ runnableExamples: doAssert toLowerAscii("FooBar!") == "foobar!" toImpl toLowerAscii proc toUpperAscii*(c: char): char {.noSideEffect, procvar, rtl, extern: "nsuToUpperAsciiChar".} = - ## Converts `c` into upper case. + ## Converts character `c` into upper case. ## ## This works only for the letters ``A-Z``. See `unicode.toUpper ## `_ for a version that works for any Unicode ## character. + ## + ## See also: + ## * `isLowerAscii proc<#isLowerAscii,char>`_ + ## * `toUpperAscii proc<#toUpperAscii,string>`_ for converting a string + ## * `capitalizeAscii proc<#capitalizeAscii,string>`_ runnableExamples: doAssert toUpperAscii('a') == 'A' doAssert toUpperAscii('E') == 'E' @@ -299,20 +266,27 @@ proc toUpperAscii*(c: char): char {.noSideEffect, procvar, proc toUpperAscii*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuToUpperAsciiStr".} = - ## Converts `s` into upper case. + ## Converts string `s` into upper case. ## ## This works only for the letters ``A-Z``. See `unicode.toUpper ## `_ for a version that works for any Unicode ## character. + ## + ## See also: + ## * `capitalizeAscii proc<#capitalizeAscii,string>`_ runnableExamples: doAssert toUpperAscii("FooBar!") == "FOOBAR!" toImpl toUpperAscii proc capitalizeAscii*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuCapitalizeAscii".} = - ## Converts the first character of `s` into upper case. + ## Converts the first character of string `s` into upper case. ## ## This works only for the letters ``A-Z``. + ## Use `Unicode module`_ for UTF-8 support. + ## + ## See also: + ## * `toUpperAscii proc<#toUpperAscii,char>`_ runnableExamples: doAssert capitalizeAscii("foo") == "Foo" doAssert capitalizeAscii("-bar") == "-bar" @@ -325,6 +299,9 @@ proc normalize*(s: string): string {.noSideEffect, procvar, ## ## That means to convert it to lower case and remove any '_'. This ## should NOT be used to normalize Nim identifier names. + ## + ## See also: + ## * `toLowerAscii proc<#toLowerAscii,string>`_ runnableExamples: doAssert normalize("Foo_bar") == "foobar" doAssert normalize("Foo Bar") == "foo bar" @@ -343,9 +320,9 @@ proc cmpIgnoreCase*(a, b: string): int {.noSideEffect, rtl, extern: "nsuCmpIgnoreCase", procvar.} = ## Compares two strings in a case insensitive manner. Returns: ## - ## | 0 iff a == b - ## | < 0 iff a < b - ## | > 0 iff a > b + ## | 0 if a == b + ## | < 0 if a < b + ## | > 0 if a > b runnableExamples: doAssert cmpIgnoreCase("FooBar", "foobar") == 0 doAssert cmpIgnoreCase("bar", "Foo") < 0 @@ -365,12 +342,14 @@ proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect, rtl, extern: "nsuCmpIgnoreStyle", procvar.} = ## Semantically the same as ``cmp(normalize(a), normalize(b))``. It ## is just optimized to not allocate temporary strings. This should - ## NOT be used to compare Nim identifier names. use `macros.eqIdent` - ## for that. Returns: + ## NOT be used to compare Nim identifier names. + ## Use `macros.eqIdent`_ for that. + ## + ## Returns: ## - ## | 0 iff a == b - ## | < 0 iff a < b - ## | > 0 iff a > b + ## | 0 if a == b + ## | < 0 if a < b + ## | > 0 if a > b runnableExamples: doAssert cmpIgnoreStyle("foo_bar", "FooBar") == 0 doAssert cmpIgnoreStyle("foo_bar_5", "FooBar4") > 0 @@ -394,51 +373,8 @@ proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect, inc i inc j -proc strip*(s: string, leading = true, trailing = true, - chars: set[char] = Whitespace): string - {.noSideEffect, rtl, extern: "nsuStrip".} = - ## Strips leading or trailing `chars` from `s` and returns - ## the resulting string. - ## - ## If `leading` is true, leading `chars` are stripped. - ## If `trailing` is true, trailing `chars` are stripped. - ## If both are false, the string is returned unchanged. - runnableExamples: - doAssert " vhellov ".strip().strip(trailing = false, chars = {'v'}) == "hellov" - var - first = 0 - last = len(s)-1 - if leading: - while first <= last and s[first] in chars: inc(first) - if trailing: - while last >= 0 and s[last] in chars: dec(last) - result = substr(s, first, last) - -proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} = - ## Converts a character `c` to its octal representation. - ## - ## The resulting string may not have a leading zero. Its length is always - ## exactly 3. - runnableExamples: - doAssert toOctal('!') == "041" - result = newString(3) - var val = ord(c) - for i in countdown(2, 0): - result[i] = chr(val mod 8 + ord('0')) - val = val div 8 - -proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, - extern: "nsuIsNilOrEmpty", - deprecated: "use 'x.len == 0' instead".} = - ## Checks if `s` is nil or empty. - result = len(s) == 0 -proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrWhitespace".} = - ## Checks if `s` is nil or consists entirely of whitespace characters. - result = true - for c in s: - if not c.isSpaceAscii(): - return false +# --------- Private templates for different split separators ----------- proc substrEq(s: string, pos: int, substr: string): bool = var i = 0 @@ -447,8 +383,6 @@ proc substrEq(s: string, pos: int, substr: string): bool = inc i return i == length -# --------- Private templates for different split separators ----------- - template stringHasSep(s: string, index: int, seps: set[char]): bool = s[index] in seps @@ -459,7 +393,7 @@ template stringHasSep(s: string, index: int, sep: string): bool = s.substrEq(index, sep) template splitCommon(s, sep, maxsplit, sepLen) = - ## Common code for split procedures + ## Common code for split procs var last = 0 var splits = maxsplit @@ -487,6 +421,42 @@ template oldSplit(s, seps, maxsplit) = if splits == 0: break dec(splits) +template accResult(iter: untyped) = + result = @[] + for x in iter: add(result, x) + + +iterator split*(s: string, sep: char, maxsplit: int = -1): string = + ## Splits the string `s` into substrings using a single separator. + ## + ## Substrings are separated by the character `sep`. + ## The code: + ## + ## .. code-block:: nim + ## for word in split(";;this;is;an;;example;;;", ';'): + ## writeLine(stdout, word) + ## + ## Results in: + ## + ## .. code-block:: + ## "" + ## "" + ## "this" + ## "is" + ## "an" + ## "" + ## "example" + ## "" + ## "" + ## "" + ## + ## See also: + ## * `rsplit iterator<#rsplit.i,string,char,int>`_ + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ + ## * `split proc<#split,string,char,int>`_ + splitCommon(s, sep, maxsplit, 1) + iterator split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a group of separators. @@ -529,87 +499,21 @@ iterator split*(s: string, seps: set[char] = Whitespace, ## "08" ## "08.398990" ## + ## See also: + ## * `rsplit iterator<#rsplit.i,string,set[char],int>`_ + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ + ## * `split proc<#split,string,set[char],int>`_ splitCommon(s, seps, maxsplit, 1) -iterator splitWhitespace*(s: string, maxsplit: int = -1): string = - ## Splits the string ``s`` at whitespace stripping leading and trailing - ## whitespace if necessary. If ``maxsplit`` is specified and is positive, - ## no more than ``maxsplit`` splits is made. - ## - ## The following code: - ## - ## .. code-block:: nim - ## let s = " foo \t bar baz " - ## for ms in [-1, 1, 2, 3]: - ## echo "------ maxsplit = ", ms, ":" - ## for item in s.splitWhitespace(maxsplit=ms): - ## echo '"', item, '"' - ## - ## ...results in: - ## - ## .. code-block:: - ## ------ maxsplit = -1: - ## "foo" - ## "bar" - ## "baz" - ## ------ maxsplit = 1: - ## "foo" - ## "bar baz " - ## ------ maxsplit = 2: - ## "foo" - ## "bar" - ## "baz " - ## ------ maxsplit = 3: - ## "foo" - ## "bar" - ## "baz" - ## - oldSplit(s, Whitespace, maxsplit) - -template accResult(iter: untyped) = - result = @[] - for x in iter: add(result, x) - -proc splitWhitespace*(s: string, maxsplit: int = -1): seq[string] {.noSideEffect, - rtl, extern: "nsuSplitWhitespace".} = - ## The same as the `splitWhitespace <#splitWhitespace.i,string,int>`_ - ## iterator, but is a proc that returns a sequence of substrings. - accResult(splitWhitespace(s, maxsplit)) - -iterator split*(s: string, sep: char, maxsplit: int = -1): string = - ## Splits the string `s` into substrings using a single separator. +iterator split*(s: string, sep: string, maxsplit: int = -1): string = + ## Splits the string `s` into substrings using a string separator. ## - ## Substrings are separated by the character `sep`. + ## Substrings are separated by the string `sep`. ## The code: ## ## .. code-block:: nim - ## for word in split(";;this;is;an;;example;;;", ';'): - ## writeLine(stdout, word) - ## - ## Results in: - ## - ## .. code-block:: - ## "" - ## "" - ## "this" - ## "is" - ## "an" - ## "" - ## "example" - ## "" - ## "" - ## "" - ## - splitCommon(s, sep, maxsplit, 1) - -iterator split*(s: string, sep: string, maxsplit: int = -1): string = - ## Splits the string `s` into substrings using a string separator. - ## - ## Substrings are separated by the string `sep`. - ## The code: - ## - ## .. code-block:: nim - ## for word in split("thisDATAisDATAcorrupted", "DATA"): + ## for word in split("thisDATAisDATAcorrupted", "DATA"): ## writeLine(stdout, word) ## ## Results in: @@ -619,8 +523,14 @@ iterator split*(s: string, sep: string, maxsplit: int = -1): string = ## "is" ## "corrupted" ## + ## See also: + ## * `rsplit iterator<#rsplit.i,string,string,int,bool>`_ + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ + ## * `split proc<#split,string,string,int>`_ splitCommon(s, sep, maxsplit, sep.len) + template rsplitCommon(s, sep, maxsplit, sepLen) = ## Common code for rsplit functions var @@ -645,14 +555,14 @@ template rsplitCommon(s, sep, maxsplit, sepLen) = dec(first) last = first -iterator rsplit*(s: string, seps: set[char] = Whitespace, +iterator rsplit*(s: string, sep: char, maxsplit: int = -1): string = ## Splits the string `s` into substrings from the right using a ## string separator. Works exactly the same as `split iterator ## <#split.i,string,char,int>`_ except in reverse order. ## ## .. code-block:: nim - ## for piece in "foo bar".rsplit(WhiteSpace): + ## for piece in "foo:bar".rsplit(':'): ## echo piece ## ## Results in: @@ -661,17 +571,23 @@ iterator rsplit*(s: string, seps: set[char] = Whitespace, ## "bar" ## "foo" ## - ## Substrings are separated from the right by the set of chars `seps` - rsplitCommon(s, seps, maxsplit, 1) + ## Substrings are separated from the right by the char `sep`. + ## + ## See also: + ## * `split iterator<#split.i,string,char,int>`_ + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ + ## * `rsplit proc<#rsplit,string,char,int>`_ + rsplitCommon(s, sep, maxsplit, 1) -iterator rsplit*(s: string, sep: char, +iterator rsplit*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = ## Splits the string `s` into substrings from the right using a ## string separator. Works exactly the same as `split iterator ## <#split.i,string,char,int>`_ except in reverse order. ## ## .. code-block:: nim - ## for piece in "foo:bar".rsplit(':'): + ## for piece in "foo bar".rsplit(WhiteSpace): ## echo piece ## ## Results in: @@ -680,8 +596,14 @@ iterator rsplit*(s: string, sep: char, ## "bar" ## "foo" ## - ## Substrings are separated from the right by the char `sep` - rsplitCommon(s, sep, maxsplit, 1) + ## Substrings are separated from the right by the set of chars `seps` + ## + ## See also: + ## * `split iterator<#split.i,string,set[char],int>`_ + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ + ## * `rsplit proc<#rsplit,string,set[char],int>`_ + rsplitCommon(s, seps, maxsplit, 1) iterator rsplit*(s: string, sep: string, maxsplit: int = -1, keepSeparators: bool = false): string = @@ -700,6 +622,12 @@ iterator rsplit*(s: string, sep: string, maxsplit: int = -1, ## "foo" ## ## Substrings are separated from the right by the string `sep` + ## + ## See also: + ## * `split iterator<#split.i,string,string,int>`_ + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ + ## * `rsplit proc<#rsplit,string,string,int>`_ rsplitCommon(s, sep, maxsplit, sep.len) iterator splitLines*(s: string, keepEol = false): string = @@ -726,6 +654,10 @@ iterator splitLines*(s: string, keepEol = false): string = ## "" ## "example" ## "" + ## + ## See also: + ## * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ + ## * `splitLines proc<#splitLines,string>`_ var first = 0 var last = 0 var eolpos = 0 @@ -747,76 +679,102 @@ iterator splitLines*(s: string, keepEol = false): string = first = last -proc splitLines*(s: string, keepEol = false): seq[string] {.noSideEffect, - rtl, extern: "nsuSplitLines".} = - ## The same as the `splitLines <#splitLines.i,string>`_ iterator, but is a - ## proc that returns a sequence of substrings. - accResult(splitLines(s, keepEol=keepEol)) - -proc countLines*(s: string): int {.noSideEffect, - rtl, extern: "nsuCountLines".} = - ## Returns the number of lines in the string `s`. +iterator splitWhitespace*(s: string, maxsplit: int = -1): string = + ## Splits the string ``s`` at whitespace stripping leading and trailing + ## whitespace if necessary. If ``maxsplit`` is specified and is positive, + ## no more than ``maxsplit`` splits is made. ## - ## This is the same as ``len(splitLines(s))``, but much more efficient - ## because it doesn't modify the string creating temporal objects. Every - ## `character literal `_ newline combination - ## (CR, LF, CR-LF) is supported. + ## The following code: ## - ## In this context, a line is any string seperated by a newline combination. - ## A line can be an empty string. - runnableExamples: - doAssert countLines("First line\l and second line.") == 2 - result = 1 - var i = 0 - while i < s.len: - case s[i] - of '\c': - if i+1 < s.len and s[i+1] == '\l': inc i - inc result - of '\l': inc result - else: discard - inc i + ## .. code-block:: nim + ## let s = " foo \t bar baz " + ## for ms in [-1, 1, 2, 3]: + ## echo "------ maxsplit = ", ms, ":" + ## for item in s.splitWhitespace(maxsplit=ms): + ## echo '"', item, '"' + ## + ## ...results in: + ## + ## .. code-block:: + ## ------ maxsplit = -1: + ## "foo" + ## "bar" + ## "baz" + ## ------ maxsplit = 1: + ## "foo" + ## "bar baz " + ## ------ maxsplit = 2: + ## "foo" + ## "bar" + ## "baz " + ## ------ maxsplit = 3: + ## "foo" + ## "bar" + ## "baz" + ## + ## See also: + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ + oldSplit(s, Whitespace, maxsplit) + -proc split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): seq[string] {. - noSideEffect, rtl, extern: "nsuSplitCharSet".} = - ## The same as the `split iterator <#split.i,string,set[char],int>`_, but is a - ## proc that returns a sequence of substrings. - runnableExamples: - doAssert "a,b;c".split({',', ';'}) == @["a", "b", "c"] - doAssert "".split({' '}) == @[""] - accResult(split(s, seps, maxsplit)) proc split*(s: string, sep: char, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitChar".} = - ## The same as the `split iterator <#split.i,string,char,int>`_, but is a proc - ## that returns a sequence of substrings. + ## The same as the `split iterator <#split.i,string,char,int>`_ (see its + ## documentation), but is a proc that returns a sequence of substrings. + ## + ## See also: + ## * `split iterator <#split.i,string,char,int>`_ + ## * `rsplit proc<#rsplit,string,char,int>`_ + ## * `splitLines proc<#splitLines,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ runnableExamples: doAssert "a,b,c".split(',') == @["a", "b", "c"] doAssert "".split(' ') == @[""] accResult(split(s, sep, maxsplit)) +proc split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): seq[string] {. + noSideEffect, rtl, extern: "nsuSplitCharSet".} = + ## The same as the `split iterator <#split.i,string,set[char],int>`_ (see its + ## documentation), but is a proc that returns a sequence of substrings. + ## + ## See also: + ## * `split iterator <#split.i,string,set[char],int>`_ + ## * `rsplit proc<#rsplit,string,set[char],int>`_ + ## * `splitLines proc<#splitLines,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ + runnableExamples: + doAssert "a,b;c".split({',', ';'}) == @["a", "b", "c"] + doAssert "".split({' '}) == @[""] + accResult(split(s, seps, maxsplit)) + proc split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitString".} = ## Splits the string `s` into substrings using a string separator. ## ## Substrings are separated by the string `sep`. This is a wrapper around the ## `split iterator <#split.i,string,string,int>`_. + ## + ## See also: + ## * `split iterator <#split.i,string,string,int>`_ + ## * `rsplit proc<#rsplit,string,string,int>`_ + ## * `splitLines proc<#splitLines,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ runnableExamples: doAssert "a,b,c".split(",") == @["a", "b", "c"] doAssert "a man a plan a canal panama".split("a ") == @["", "man ", "plan ", "canal panama"] doAssert "".split("Elon Musk") == @[""] doAssert "a largely spaced sentence".split(" ") == @["a", "", "largely", "", "", "", "spaced", "sentence"] - doAssert "a largely spaced sentence".split(" ", maxsplit=1) == @["a", " largely spaced sentence"] doAssert(sep.len > 0) accResult(split(s, sep, maxsplit)) -proc rsplit*(s: string, seps: set[char] = Whitespace, - maxsplit: int = -1): seq[string] - {.noSideEffect, rtl, extern: "nsuRSplitCharSet".} = - ## The same as the `rsplit iterator <#rsplit.i,string,set[char],int>`_, but is a - ## proc that returns a sequence of substrings. +proc rsplit*(s: string, sep: char, maxsplit: int = -1): seq[string] + {.noSideEffect, rtl, extern: "nsuRSplitChar".} = + ## The same as the `rsplit iterator <#rsplit.i,string,char,int>`_, but is a proc + ## that returns a sequence of substrings. ## ## A possible common use case for `rsplit` is path manipulation, ## particularly on systems that don't use a common delimiter. @@ -825,20 +783,26 @@ proc rsplit*(s: string, seps: set[char] = Whitespace, ## do the following to get the tail of the path: ## ## .. code-block:: nim - ## var tailSplit = rsplit("Root#Object#Method#Index", {'#'}, maxsplit=1) + ## var tailSplit = rsplit("Root#Object#Method#Index", '#', maxsplit=1) ## ## Results in `tailSplit` containing: ## ## .. code-block:: nim ## @["Root#Object#Method", "Index"] ## - accResult(rsplit(s, seps, maxsplit)) + ## See also: + ## * `rsplit iterator <#rsplit.i,string,char,int>`_ + ## * `split proc<#split,string,char,int>`_ + ## * `splitLines proc<#splitLines,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ + accResult(rsplit(s, sep, maxsplit)) result.reverse() -proc rsplit*(s: string, sep: char, maxsplit: int = -1): seq[string] - {.noSideEffect, rtl, extern: "nsuRSplitChar".} = - ## The same as the `rsplit iterator <#rsplit.i,string,char,int>`_, but is a proc - ## that returns a sequence of substrings. +proc rsplit*(s: string, seps: set[char] = Whitespace, + maxsplit: int = -1): seq[string] + {.noSideEffect, rtl, extern: "nsuRSplitCharSet".} = + ## The same as the `rsplit iterator <#rsplit.i,string,set[char],int>`_, but is a + ## proc that returns a sequence of substrings. ## ## A possible common use case for `rsplit` is path manipulation, ## particularly on systems that don't use a common delimiter. @@ -847,19 +811,24 @@ proc rsplit*(s: string, sep: char, maxsplit: int = -1): seq[string] ## do the following to get the tail of the path: ## ## .. code-block:: nim - ## var tailSplit = rsplit("Root#Object#Method#Index", '#', maxsplit=1) + ## var tailSplit = rsplit("Root#Object#Method#Index", {'#'}, maxsplit=1) ## ## Results in `tailSplit` containing: ## ## .. code-block:: nim ## @["Root#Object#Method", "Index"] ## - accResult(rsplit(s, sep, maxsplit)) + ## See also: + ## * `rsplit iterator <#rsplit.i,string,set[char],int>`_ + ## * `split proc<#split,string,set[char],int>`_ + ## * `splitLines proc<#splitLines,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ + accResult(rsplit(s, seps, maxsplit)) result.reverse() proc rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuRSplitString".} = - ## The same as the `rsplit iterator <#rsplit.i,string,string,int>`_, but is a proc + ## The same as the `rsplit iterator <#rsplit.i,string,string,int,bool>`_, but is a proc ## that returns a sequence of substrings. ## ## A possible common use case for `rsplit` is path manipulation, @@ -876,9 +845,13 @@ proc rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string] ## .. code-block:: nim ## @["Root#Object#Method", "Index"] ## + ## See also: + ## * `rsplit iterator <#rsplit.i,string,string,int,bool>`_ + ## * `split proc<#split,string,string,int>`_ + ## * `splitLines proc<#splitLines,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ runnableExamples: doAssert "a largely spaced sentence".rsplit(" ", maxsplit=1) == @["a largely spaced", "sentence"] - doAssert "a,b,c".rsplit(",") == @["a", "b", "c"] doAssert "a man a plan a canal panama".rsplit("a ") == @["", "man ", "plan ", "canal panama"] doAssert "".rsplit("Elon Musk") == @[""] @@ -886,6 +859,75 @@ proc rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string] accResult(rsplit(s, sep, maxsplit)) result.reverse() +proc splitLines*(s: string, keepEol = false): seq[string] {.noSideEffect, + rtl, extern: "nsuSplitLines".} = + ## The same as the `splitLines iterator<#splitLines.i,string>`_ (see its + ## documentation), but is a proc that returns a sequence of substrings. + ## + ## See also: + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ + ## * `countLines proc<#countLines,string>`_ + accResult(splitLines(s, keepEol=keepEol)) + +proc splitWhitespace*(s: string, maxsplit: int = -1): seq[string] {.noSideEffect, + rtl, extern: "nsuSplitWhitespace".} = + ## The same as the `splitWhitespace iterator <#splitWhitespace.i,string,int>`_ + ## (see its documentation), but is a proc that returns a sequence of substrings. + ## + ## See also: + ## * `splitWhitespace iterator <#splitWhitespace.i,string,int>`_ + ## * `splitLines proc<#splitLines,string>`_ + accResult(splitWhitespace(s, maxsplit)) + +proc toBin*(x: BiggestInt, len: Positive): string {.noSideEffect, + rtl, extern: "nsuToBin".} = + ## Converts `x` into its binary representation. + ## + ## The resulting string is always `len` characters long. No leading ``0b`` + ## prefix is generated. + runnableExamples: + let + a = 29 + b = 257 + doAssert a.toBin(8) == "00011101" + doAssert b.toBin(8) == "00000001" + doAssert b.toBin(9) == "100000001" + var + mask: BiggestInt = 1 + shift: BiggestInt = 0 + assert(len > 0) + result = newString(len) + for j in countdown(len-1, 0): + result[j] = chr(int((x and mask) shr shift) + ord('0')) + shift = shift + 1 + mask = mask shl 1 + +proc toOct*(x: BiggestInt, len: Positive): string {.noSideEffect, + rtl, extern: "nsuToOct".} = + ## Converts `x` into its octal representation. + ## + ## The resulting string is always `len` characters long. No leading ``0o`` + ## prefix is generated. + ## + ## Do not confuse it with `toOctal proc<#toOctal,char>`_. + runnableExamples: + let + a = 62 + b = 513 + doAssert a.toOct(3) == "076" + doAssert b.toOct(3) == "001" + doAssert b.toOct(5) == "01001" + var + mask: BiggestInt = 7 + shift: BiggestInt = 0 + assert(len > 0) + result = newString(len) + for j in countdown(len-1, 0): + result[j] = chr(int((x and mask) shr shift) + ord('0')) + shift = shift + 3 + mask = mask shl 3 + proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, rtl, extern: "nsuToHex".} = ## Converts `x` to its hexadecimal representation. @@ -893,8 +935,12 @@ proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, ## The resulting string will be exactly `len` characters long. No prefix like ## ``0x`` is generated. `x` is treated as an unsigned value. runnableExamples: - doAssert toHex(1984, 6) == "0007C0" - doAssert toHex(1984, 2) == "C0" + let + a = 62 + b = 4097 + doAssert a.toHex(3) == "03E" + doAssert b.toHex(3) == "001" + doAssert b.toHex(4) == "1001" const HexChars = "0123456789ABCDEF" var @@ -917,6 +963,18 @@ proc toHex*(s: string): string {.noSideEffect, rtl.} = ## ## The output is twice the input long. No prefix like ## ``0x`` is generated. + ## + ## See also: + ## * `parseHexStr proc<#parseHexStr,string>`_ for the reverse operation + runnableExamples: + let + a = "1" + b = "A" + c = "\0\255" + doAssert a.toHex() == "31" + doAssert b.toHex() == "41" + doAssert c.toHex() == "00FF" + const HexChars = "0123456789ABCDEF" result = newString(s.len * 2) for pos, c in s: @@ -925,6 +983,25 @@ proc toHex*(s: string): string {.noSideEffect, rtl.} = n = n shr 4 result[pos * 2] = HexChars[n] +proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} = + ## Converts a character `c` to its octal representation. + ## + ## The resulting string may not have a leading zero. Its length is always + ## exactly 3. + ## + ## Do not confuse it with `toOct proc<#toOct,BiggestInt,Positive>`_. + runnableExamples: + doAssert toOctal('1') == "061" + doAssert toOctal('A') == "101" + doAssert toOctal('a') == "141" + doAssert toOctal('!') == "041" + + result = newString(3) + var val = ord(c) + for i in countdown(2, 0): + result[i] = chr(val mod 8 + ord('0')) + val = val div 8 + proc intToStr*(x: int, minchars: Positive = 1): string {.noSideEffect, rtl, extern: "nsuIntToStr".} = ## Converts `x` to its decimal representation. @@ -980,9 +1057,10 @@ proc parseBiggestUInt*(s: string): BiggestUInt {.noSideEffect, procvar, proc parseFloat*(s: string): float {.noSideEffect, procvar, rtl, extern: "nsuParseFloat".} = - ## Parses a decimal floating point value contained in `s`. If `s` is not - ## a valid floating point number, `ValueError` is raised. ``NAN``, - ## ``INF``, ``-INF`` are also supported (case insensitive comparison). + ## Parses a decimal floating point value contained in `s`. + ## + ## If `s` is not a valid floating point number, `ValueError` is raised. + ##``NAN``, ``INF``, ``-INF`` are also supported (case insensitive comparison). runnableExamples: doAssert parseFloat("3.14") == 3.14 doAssert parseFloat("inf") == 1.0/0 @@ -997,6 +1075,13 @@ proc parseBinInt*(s: string): int {.noSideEffect, procvar, ## If `s` is not a valid binary integer, `ValueError` is raised. `s` can have ## one of the following optional prefixes: ``0b``, ``0B``. Underscores within ## `s` are ignored. + runnableExamples: + let + a = "0b11_0101" + b = "111" + doAssert a.parseBinInt() == 53 + doAssert b.parseBinInt() == 7 + let L = parseutils.parseBin(s, result, 0) if L != s.len or L == 0: raise newException(ValueError, "invalid binary integer: " & s) @@ -1042,11 +1127,20 @@ proc parseHexStr*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuParseHexStr".} = ## Convert hex-encoded string to byte string, e.g.: ## - ## .. code-block:: nim - ## hexToStr("00ff") == "\0\255" - ## ## Raises ``ValueError`` for an invalid hex values. The comparison is ## case-insensitive. + ## + ## See also: + ## * `toHex proc<#toHex,string>`_ for the reverse operation + runnableExamples: + let + a = "41" + b = "3161" + c = "00ff" + doAssert parseHexStr(a) == "A" + doAssert parseHexStr(b) == "1a" + doAssert parseHexStr(c) == "\0\255" + if s.len mod 2 != 0: raise newException(ValueError, "Incorrect hex string len") result = newString(s.len div 2) @@ -1067,6 +1161,10 @@ proc parseBool*(s: string): bool = ## returns `true`. If ``s`` is one of the following values: ``n, no, false, ## 0, off``, then returns `false`. If ``s`` is something else a ## ``ValueError`` exception is raised. + runnableExamples: + let a = "n" + doAssert parseBool(a) == false + case normalize(s) of "y", "yes", "true", "1", "on": result = true of "n", "no", "false", "0", "off": result = false @@ -1095,39 +1193,41 @@ proc parseEnum*[T: enum](s: string, default: T): T = proc repeat*(c: char, count: Natural): string {.noSideEffect, rtl, extern: "nsuRepeatChar".} = ## Returns a string of length `count` consisting only of - ## the character `c`. You can use this proc to left align strings. Example: - ## - ## .. code-block:: nim - ## proc tabexpand(indent: int, text: string, tabsize: int = 4) = - ## echo '\t'.repeat(indent div tabsize), ' '.repeat(indent mod tabsize), - ## text - ## - ## tabexpand(4, "At four") - ## tabexpand(5, "At five") - ## tabexpand(6, "At six") + ## the character `c`. + runnableExamples: + let a = 'z' + doAssert a.repeat(5) == "zzzzz" result = newString(count) for i in 0..count-1: result[i] = c proc repeat*(s: string, n: Natural): string {.noSideEffect, rtl, extern: "nsuRepeatStr".} = - ## Returns String `s` concatenated `n` times. Example: - ## - ## .. code-block:: nim - ## echo "+++ STOP ".repeat(4), "+++" + ## Returns string `s` concatenated `n` times. + runnableExamples: + doAssert "+ foo +".repeat(3) == "+ foo ++ foo ++ foo +" + result = newStringOfCap(n * s.len) for i in 1..n: result.add(s) -template spaces*(n: Natural): string = repeat(' ', n) - ## Returns a String with `n` space characters. You can use this proc - ## to left align strings. Example: +proc spaces*(n: Natural): string {.inline.} = + ## Returns a string with `n` space characters. You can use this proc + ## to left align strings. ## - ## .. code-block:: nim - ## let - ## width = 15 - ## text1 = "Hello user!" - ## text2 = "This is a very long string" - ## echo text1 & spaces(max(0, width - text1.len)) & "|" - ## echo text2 & spaces(max(0, width - text2.len)) & "|" + ## See also: + ## * `align proc<#align,string,Natural,Char>`_ + ## * `alignLeft proc<#alignLeft,string,Natural,Char>`_ + ## * `indent proc<#indent,string,Natural,string>`_ + ## * `center proc<#center,string,int,char>`_ + runnableExamples: + let + width = 15 + text1 = "Hello user!" + text2 = "This is a very long string" + doAssert text1 & spaces(max(0, width - text1.len)) & "|" == + "Hello user! |" + doAssert text2 & spaces(max(0, width - text2.len)) & "|" == + "This is a very long string|" + repeat(' ', n) proc align*(s: string, count: Natural, padding = ' '): string {. noSideEffect, rtl, extern: "nsuAlignString".} = @@ -1136,13 +1236,18 @@ proc align*(s: string, count: Natural, padding = ' '): string {. ## `padding` characters (by default spaces) are added before `s` resulting in ## right alignment. If ``s.len >= count``, no spaces are added and `s` is ## returned unchanged. If you need to left align a string use the `alignLeft - ## proc <#alignLeft>`_. Example: + ## proc <#alignLeft,string,Natural,Char>`_. ## - ## .. code-block:: nim - ## assert align("abc", 4) == " abc" - ## assert align("a", 0) == "a" - ## assert align("1232", 6) == " 1232" - ## assert align("1232", 6, '#') == "##1232" + ## See also: + ## * `alignLeft proc<#alignLeft,string,Natural,Char>`_ + ## * `spaces proc<#spaces,Natural>`_ + ## * `indent proc<#indent,string,Natural,string>`_ + ## * `center proc<#center,string,int,char>`_ + runnableExamples: + assert align("abc", 4) == " abc" + assert align("a", 0) == "a" + assert align("1232", 6) == " 1232" + assert align("1232", 6, '#') == "##1232" if s.len < count: result = newString(count) let spaces = count - s.len @@ -1157,13 +1262,18 @@ proc alignLeft*(s: string, count: Natural, padding = ' '): string {.noSideEffect ## `padding` characters (by default spaces) are added after `s` resulting in ## left alignment. If ``s.len >= count``, no spaces are added and `s` is ## returned unchanged. If you need to right align a string use the `align - ## proc <#align>`_. Example: + ## proc <#align,string,Natural,Char>`_. ## - ## .. code-block:: nim - ## assert alignLeft("abc", 4) == "abc " - ## assert alignLeft("a", 0) == "a" - ## assert alignLeft("1232", 6) == "1232 " - ## assert alignLeft("1232", 6, '#') == "1232##" + ## See also: + ## * `align proc<#align,string,Natural,Char>`_ + ## * `spaces proc<#spaces,Natural>`_ + ## * `indent proc<#indent,string,Natural,string>`_ + ## * `center proc<#center,string,int,char>`_ + runnableExamples: + assert alignLeft("abc", 4) == "abc " + assert alignLeft("a", 0) == "a" + assert alignLeft("1232", 6) == "1232 " + assert alignLeft("1232", 6, '#') == "1232##" if s.len < count: result = newString(count) if s.len > 0: @@ -1173,83 +1283,55 @@ proc alignLeft*(s: string, count: Natural, padding = ' '): string {.noSideEffect else: result = s -iterator tokenize*(s: string, seps: set[char] = Whitespace): tuple[ - token: string, isSep: bool] = - ## Tokenizes the string `s` into substrings. - ## - ## Substrings are separated by a substring containing only `seps`. - ## Examples: - ## - ## .. code-block:: nim - ## for word in tokenize(" this is an example "): - ## writeLine(stdout, word) +proc center*(s: string, width: int, fillChar: char = ' '): string {. + noSideEffect, rtl, extern: "nsuCenterString".} = + ## Return the contents of `s` centered in a string `width` long using + ## `fillChar` (default: space) as padding. ## - ## Results in: + ## The original string is returned if `width` is less than or equal + ## to `s.len`. ## - ## .. code-block:: nim - ## (" ", true) - ## ("this", false) - ## (" ", true) - ## ("is", false) - ## (" ", true) - ## ("an", false) - ## (" ", true) - ## ("example", false) - ## (" ", true) - var i = 0 - while true: - var j = i - var isSep = j < s.len and s[j] in seps - while j < s.len and (s[j] in seps) == isSep: inc(j) - if j > i: - yield (substr(s, i, j-1), isSep) - else: - break - i = j - -proc wordWrap*(s: string, maxLineWidth = 80, - splitLongWords = true, - seps: set[char] = Whitespace, - newLine = "\n"): string {. - noSideEffect, rtl, extern: "nsuWordWrap", - deprecated: "use wrapWords in std/wordwrap instead".} = - ## Word wraps `s`. - result = newStringOfCap(s.len + s.len shr 6) - var spaceLeft = maxLineWidth - var lastSep = "" - for word, isSep in tokenize(s, seps): - if isSep: - lastSep = word - spaceLeft = spaceLeft - len(word) - continue - if len(word) > spaceLeft: - if splitLongWords and len(word) > maxLineWidth: - result.add(substr(word, 0, spaceLeft-1)) - var w = spaceLeft - var wordLeft = len(word) - spaceLeft - while wordLeft > 0: - result.add(newLine) - var L = min(maxLineWidth, wordLeft) - spaceLeft = maxLineWidth - L - result.add(substr(word, w, w+L-1)) - inc(w, L) - dec(wordLeft, L) - else: - spaceLeft = maxLineWidth - len(word) - result.add(newLine) - result.add(word) + ## See also: + ## * `align proc<#align,string,Natural,Char>`_ + ## * `alignLeft proc<#alignLeft,string,Natural,Char>`_ + ## * `spaces proc<#spaces,Natural>`_ + ## * `indent proc<#indent,string,Natural,string>`_ + runnableExamples: + let a = "foo" + doAssert a.center(2) == "foo" + doAssert a.center(5) == " foo " + doAssert a.center(6) == " foo " + if width <= s.len: return s + result = newString(width) + # Left padding will be one fillChar + # smaller if there are an odd number + # of characters + let + charsLeft = (width - s.len) + leftPadding = charsLeft div 2 + for i in 0 ..< width: + if i >= leftPadding and i < leftPadding + s.len: + # we are where the string should be located + result[i] = s[i-leftPadding] else: - spaceLeft = spaceLeft - len(word) - result.add(lastSep & word) - lastSep.setLen(0) + # we are either before or after where + # the string s should go + result[i] = fillChar proc indent*(s: string, count: Natural, padding: string = " "): string {.noSideEffect, rtl, extern: "nsuIndent".} = ## Indents each line in ``s`` by ``count`` amount of ``padding``. ## ## **Note:** This does not preserve the new line characters used in ``s``. + ## + ## See also: + ## * `align proc<#align,string,Natural,Char>`_ + ## * `alignLeft proc<#alignLeft,string,Natural,Char>`_ + ## * `spaces proc<#spaces,Natural>`_ + ## * `unindent proc<#unindent,string,Natural,string>`_ runnableExamples: - doAssert indent("First line\c\l and second line.", 2) == " First line\l and second line." + doAssert indent("First line\c\l and second line.", 2) == + " First line\l and second line." result = "" var i = 0 for line in s.splitLines(): @@ -1266,8 +1348,15 @@ proc unindent*(s: string, count: Natural, padding: string = " "): string ## Sometimes called `dedent`:idx: ## ## **Note:** This does not preserve the new line characters used in ``s``. + ## + ## See also: + ## * `align proc<#align,string,Natural,Char>`_ + ## * `alignLeft proc<#alignLeft,string,Natural,Char>`_ + ## * `spaces proc<#spaces,Natural>`_ + ## * `indent proc<#indent,string,Natural,string>`_ runnableExamples: - doAssert unindent(" First line\l and second line", 3) == "First line\land second line" + doAssert unindent(" First line\l and second line", 3) == + "First line\land second line" result = "" var i = 0 for line in s.splitLines(): @@ -1286,37 +1375,105 @@ proc unindent*(s: string): string {.noSideEffect, rtl, extern: "nsuUnindentAll".} = ## Removes all indentation composed of whitespace from each line in ``s``. ## - ## For example: + ## See also: + ## * `align proc<#align,string,Natural,Char>`_ + ## * `alignLeft proc<#alignLeft,string,Natural,Char>`_ + ## * `spaces proc<#spaces,Natural>`_ + ## * `indent proc<#indent,string,Natural,string>`_ + runnableExamples: + let x = """ + Hello + There + """.unindent() + + doAssert x == "Hello\nThere\n" + unindent(s, 1000) # TODO: Passing a 1000 is a bit hackish. + +proc delete*(s: var string, first, last: int) {.noSideEffect, + rtl, extern: "nsuDelete".} = + ## Deletes in `s` (must be declared as ``var``) the characters at positions + ## ``first ..last`` (both ends included). ## - ## .. code-block:: nim - ## const x = """ - ## Hello - ## There - ## """.unindent() + ## This modifies `s` itself, it does not return a copy. + runnableExamples: + var a = "abracadabra" + + a.delete(4, 5) + doAssert a == "abradabra" + + a.delete(1, 6) + doAssert a == "ara" + + var i = first + var j = last+1 + var newLen = len(s)-j+i + while i < newLen: + s[i] = s[j] + inc(i) + inc(j) + setLen(s, newLen) + + +proc startsWith*(s: string, prefix: char): bool {.noSideEffect, inline.} = + ## Returns true if ``s`` starts with character ``prefix``. ## - ## doAssert x == "Hello\nThere\n" - unindent(s, 1000) # TODO: Passing a 1000 is a bit hackish. + ## See also: + ## * `endsWith proc<#endsWith,string,char>`_ + ## * `continuesWith proc<#continuesWith,string,string,Natural>`_ + ## * `removePrefix proc<#removePrefix,string,char>`_ + runnableExamples: + let a = "abracadabra" + doAssert a.startsWith('a') == true + doAssert a.startsWith('b') == false + result = s.len > 0 and s[0] == prefix proc startsWith*(s, prefix: string): bool {.noSideEffect, rtl, extern: "nsuStartsWith".} = - ## Returns true iff ``s`` starts with ``prefix``. + ## Returns true if ``s`` starts with string ``prefix``. ## ## If ``prefix == ""`` true is returned. + ## + ## See also: + ## * `endsWith proc<#endsWith,string,string>`_ + ## * `continuesWith proc<#continuesWith,string,string,Natural>`_ + ## * `removePrefix proc<#removePrefix,string,string>`_ + runnableExamples: + let a = "abracadabra" + doAssert a.startsWith("abra") == true + doAssert a.startsWith("bra") == false var i = 0 while true: if i >= prefix.len: return true if i >= s.len or s[i] != prefix[i]: return false inc(i) -proc startsWith*(s: string, prefix: char): bool {.noSideEffect, inline.} = - ## Returns true iff ``s`` starts with ``prefix``. - result = s.len > 0 and s[0] == prefix +proc endsWith*(s: string, suffix: char): bool {.noSideEffect, inline.} = + ## Returns true if ``s`` ends with ``suffix``. + ## + ## See also: + ## * `startsWith proc<#startsWith,string,char>`_ + ## * `continuesWith proc<#continuesWith,string,string,Natural>`_ + ## * `removeSuffix proc<#removeSuffix,string,char>`_ + runnableExamples: + let a = "abracadabra" + doAssert a.endsWith('a') == true + doAssert a.endsWith('b') == false + result = s.len > 0 and s[s.high] == suffix proc endsWith*(s, suffix: string): bool {.noSideEffect, rtl, extern: "nsuEndsWith".} = - ## Returns true iff ``s`` ends with ``suffix``. + ## Returns true if ``s`` ends with ``suffix``. ## ## If ``suffix == ""`` true is returned. + ## + ## See also: + ## * `startsWith proc<#startsWith,string,string>`_ + ## * `continuesWith proc<#continuesWith,string,string,Natural>`_ + ## * `removeSuffix proc<#removeSuffix,string,string>`_ + runnableExamples: + let a = "abracadabra" + doAssert a.endsWith("abra") == true + doAssert a.endsWith("dab") == false var i = 0 var j = len(s) - len(suffix) while i+j <% s.len: @@ -1324,21 +1481,136 @@ proc endsWith*(s, suffix: string): bool {.noSideEffect, inc(i) if i >= suffix.len: return true -proc endsWith*(s: string, suffix: char): bool {.noSideEffect, inline.} = - ## Returns true iff ``s`` ends with ``suffix``. - result = s.len > 0 and s[s.high] == suffix - proc continuesWith*(s, substr: string, start: Natural): bool {.noSideEffect, rtl, extern: "nsuContinuesWith".} = - ## Returns true iff ``s`` continues with ``substr`` at position ``start``. + ## Returns true if ``s`` continues with ``substr`` at position ``start``. ## ## If ``substr == ""`` true is returned. + ## + ## See also: + ## * `startsWith proc<#startsWith,string,string>`_ + ## * `endsWith proc<#endsWith,string,string>`_ + runnableExamples: + let a = "abracadabra" + doAssert a.continuesWith("ca", 4) == true + doAssert a.continuesWith("ca", 5) == false + doAssert a.continuesWith("dab", 6) == true var i = 0 while true: if i >= substr.len: return true if i+start >= s.len or s[i+start] != substr[i]: return false inc(i) + +proc removePrefix*(s: var string, chars: set[char] = Newlines) {. + rtl, extern: "nsuRemovePrefixCharSet".} = + ## Removes all characters from `chars` from the start of the string `s` + ## (in-place). + ## + ## See also: + ## * `removeSuffix proc<#removeSuffix,string,set[char]>`_ + runnableExamples: + var userInput = "\r\n*~Hello World!" + userInput.removePrefix + doAssert userInput == "*~Hello World!" + userInput.removePrefix({'~', '*'}) + doAssert userInput == "Hello World!" + + var otherInput = "?!?Hello!?!" + otherInput.removePrefix({'!', '?'}) + doAssert otherInput == "Hello!?!" + + var start = 0 + while start < s.len and s[start] in chars: start += 1 + if start > 0: s.delete(0, start - 1) + +proc removePrefix*(s: var string, c: char) {. + rtl, extern: "nsuRemovePrefixChar".} = + ## Removes all occurrences of a single character (in-place) from the start + ## of a string. + ## + ## See also: + ## * `removeSuffix proc<#removeSuffix,string,char>`_ + ## * `startsWith proc<#startsWith,string,char>`_ + runnableExamples: + var ident = "pControl" + ident.removePrefix('p') + doAssert ident == "Control" + removePrefix(s, chars = {c}) + +proc removePrefix*(s: var string, prefix: string) {. + rtl, extern: "nsuRemovePrefixString".} = + ## Remove the first matching prefix (in-place) from a string. + ## + ## See also: + ## * `removeSuffix proc<#removeSuffix,string,string>`_ + ## * `startsWith proc<#startsWith,string,string>`_ + runnableExamples: + var answers = "yesyes" + answers.removePrefix("yes") + doAssert answers == "yes" + if s.startsWith(prefix): + s.delete(0, prefix.len - 1) + +proc removeSuffix*(s: var string, chars: set[char] = Newlines) {. + rtl, extern: "nsuRemoveSuffixCharSet".} = + ## Removes all characters from `chars` from the end of the string `s` + ## (in-place). + ## + ## See also: + ## * `removePrefix proc<#removePrefix,string,set[char]>`_ + runnableExamples: + var userInput = "Hello World!*~\r\n" + userInput.removeSuffix + doAssert userInput == "Hello World!*~" + userInput.removeSuffix({'~', '*'}) + doAssert userInput == "Hello World!" + + var otherInput = "Hello!?!" + otherInput.removeSuffix({'!', '?'}) + doAssert otherInput == "Hello" + + if s.len == 0: return + var last = s.high + while last > -1 and s[last] in chars: last -= 1 + s.setLen(last + 1) + +proc removeSuffix*(s: var string, c: char) {. + rtl, extern: "nsuRemoveSuffixChar".} = + ## Removes all occurrences of a single character (in-place) from the end + ## of a string. + ## + ## See also: + ## * `removePrefix proc<#removePrefix,string,char>`_ + ## * `endsWith proc<#endsWith,string,char>`_ + runnableExamples: + var table = "users" + table.removeSuffix('s') + doAssert table == "user" + + var dots = "Trailing dots......." + dots.removeSuffix('.') + doAssert dots == "Trailing dots" + + removeSuffix(s, chars = {c}) + +proc removeSuffix*(s: var string, suffix: string) {. + rtl, extern: "nsuRemoveSuffixString".} = + ## Remove the first matching suffix (in-place) from a string. + ## + ## See also: + ## * `removePrefix proc<#removePrefix,string,string>`_ + ## * `endsWith proc<#endsWith,string,string>`_ + runnableExamples: + var answers = "yeses" + answers.removeSuffix("es") + doAssert answers == "yes" + var newLen = s.len + if s.endsWith(suffix): + newLen -= len(suffix) + s.setLen(newLen) + + proc addSep*(dest: var string, sep = ", ", startLen: Natural = 0) {.noSideEffect, inline.} = ## Adds a separator to `dest` only if its length is bigger than `startLen`. @@ -1353,24 +1625,28 @@ proc addSep*(dest: var string, sep = ", ", startLen: Natural = 0) ## `startLen`. The following example creates a string describing ## an array of integers. runnableExamples: - var arr = "[" - for x in items([2, 3, 5, 7, 11]): - addSep(arr, startLen=len("[")) - add(arr, $x) - add(arr, "]") + var arr = "[" + for x in items([2, 3, 5, 7, 11]): + addSep(arr, startLen=len("[")) + add(arr, $x) + add(arr, "]") + doAssert arr == "[2, 3, 5, 7, 11]" + if dest.len > startLen: add(dest, sep) proc allCharsInSet*(s: string, theSet: set[char]): bool = - ## Returns true iff each character of `s` is in the set `theSet`. + ## Returns true if every character of `s` is in the set `theSet`. runnableExamples: doAssert allCharsInSet("aeea", {'a', 'e'}) == true doAssert allCharsInSet("", {'a', 'e'}) == true + for c in items(s): if c notin theSet: return false return true proc abbrev*(s: string, possibilities: openArray[string]): int = - ## Returns the index of the first item in ``possibilities`` which starts with ``s``, if not ambiguous. + ## Returns the index of the first item in ``possibilities`` which starts + ## with ``s``, if not ambiguous. ## ## Returns -1 if no item has been found and -2 if multiple items match. runnableExamples: @@ -1378,6 +1654,7 @@ proc abbrev*(s: string, possibilities: openArray[string]): int = doAssert abbrev("foo", ["college", "faculty", "industry"]) == -1 # Not found doAssert abbrev("fac", ["college", "faculty", "faculties"]) == -2 # Ambiguous doAssert abbrev("college", ["college", "colleges", "industry"]) == 0 + result = -1 # none found for i in 0..possibilities.len-1: if possibilities[i].startsWith(s): @@ -1391,9 +1668,10 @@ proc abbrev*(s: string, possibilities: openArray[string]): int = proc join*(a: openArray[string], sep: string = ""): string {. noSideEffect, rtl, extern: "nsuJoinSep".} = - ## Concatenates all strings in `a` separating them with `sep`. + ## Concatenates all strings in the container `a`, separating them with `sep`. runnableExamples: doAssert join(["A", "B", "Conclusion"], " -> ") == "A -> B -> Conclusion" + if len(a) > 0: var L = sep.len * (a.len-1) for i in 0..high(a): inc(L, a[i].len) @@ -1407,10 +1685,11 @@ proc join*(a: openArray[string], sep: string = ""): string {. proc join*[T: not string](a: openArray[T], sep: string = ""): string {. noSideEffect, rtl.} = - ## Converts all elements in `a` to strings using `$` and concatenates them - ## with `sep`. + ## Converts all elements in the container `a` to strings using `$`, + ## and concatenates them with `sep`. runnableExamples: doAssert join([1, 2, 3], " -> ") == "1 -> 2 -> 3" + result = "" for i, x in a: if i > 0: @@ -1441,11 +1720,11 @@ proc initSkipTable*(a: var SkipTable, sub: string) proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last = 0): int {.noSideEffect, rtl, extern: "nsuFindStrA".} = - ## Searches for `sub` in `s` inside range `start`..`last` using preprocessed table `a`. - ## If `last` is unspecified, it defaults to `s.high`. + ## Searches for `sub` in `s` inside range `start`..`last` using preprocessed + ## table `a`. If `last` is unspecified, it defaults to `s.high` (the last + ## element). ## ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. - let last = if last==0: s.high else: last subLast = sub.len - 1 @@ -1466,7 +1745,6 @@ proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last = 0): int return skip dec i inc skip, a[s[skip + subLast]] - return -1 when not (defined(js) or defined(nimdoc) or defined(nimscript)): @@ -1478,10 +1756,14 @@ else: proc find*(s: string, sub: char, start: Natural = 0, last = 0): int {.noSideEffect, rtl, extern: "nsuFindChar".} = - ## Searches for `sub` in `s` inside range `start`..`last`. - ## If `last` is unspecified, it defaults to `s.high`. + ## Searches for `sub` in `s` inside range ``start..last`` (both ends included). + ## If `last` is unspecified, it defaults to `s.high` (the last element). ## ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. + ## + ## See also: + ## * `rfind proc<#rfind,string,char,int>`_ + ## * `replace proc<#replace,string,char,char>`_ let last = if last==0: s.high else: last when nimvm: for i in int(start)..last: @@ -1498,34 +1780,72 @@ proc find*(s: string, sub: char, start: Natural = 0, last = 0): int {.noSideEffe if sub == s[i]: return i return -1 +proc find*(s: string, chars: set[char], start: Natural = 0, last = 0): int {.noSideEffect, + rtl, extern: "nsuFindCharSet".} = + ## Searches for `chars` in `s` inside range ``start..last`` (both ends included). + ## If `last` is unspecified, it defaults to `s.high` (the last element). + ## + ## If `s` contains none of the characters in `chars`, -1 is returned. + ## + ## See also: + ## * `rfind proc<#rfind,string,set[char],int>`_ + ## * `multiReplace proc<#multiReplace,string,varargs[]>`_ + let last = if last==0: s.high else: last + for i in int(start)..last: + if s[i] in chars: return i + return -1 + proc find*(s, sub: string, start: Natural = 0, last = 0): int {.noSideEffect, rtl, extern: "nsuFindStr".} = - ## Searches for `sub` in `s` inside range `start`..`last`. - ## If `last` is unspecified, it defaults to `s.high`. + ## Searches for `sub` in `s` inside range ``start..last`` (both ends included). + ## If `last` is unspecified, it defaults to `s.high` (the last element). ## ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. + ## + ## See also: + ## * `rfind proc<#rfind,string,string,int>`_ + ## * `replace proc<#replace,string,string,string>`_ if sub.len > s.len: return -1 if sub.len == 1: return find(s, sub[0], start, last) var a {.noinit.}: SkipTable initSkipTable(a, sub) result = find(a, s, sub, start, last) -proc find*(s: string, chars: set[char], start: Natural = 0, last = 0): int {.noSideEffect, - rtl, extern: "nsuFindCharSet".} = - ## Searches for `chars` in `s` inside range `start`..`last`. - ## If `last` is unspecified, it defaults to `s.high`. +proc rfind*(s: string, sub: char, start: int = -1): int {.noSideEffect, + rtl.} = + ## Searches for characer `sub` in `s` in reverse, starting at position `start` + ## (default: the last character) and going backwards to the first character. ## - ## If `s` contains none of the characters in `chars`, -1 is returned. - let last = if last==0: s.high else: last - for i in int(start)..last: + ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. + ## + ## See also: + ## * `find proc<#find,string,char,int,int>`_ + let realStart = if start == -1: s.len-1 else: start + for i in countdown(realStart, 0): + if sub == s[i]: return i + return -1 + +proc rfind*(s: string, chars: set[char], start: int = -1): int {.noSideEffect.} = + ## Searches for `chars` in `s` in reverse, starting at position `start` + ## (default: the last character) and going backwards to the first character. + ## + ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. + ## + ## See also: + ## * `find proc<#find,string,set[char],Natural,int>`_ + let realStart = if start == -1: s.len-1 else: start + for i in countdown(realStart, 0): if s[i] in chars: return i return -1 proc rfind*(s, sub: string, start: int = -1): int {.noSideEffect.} = - ## Searches for `sub` in `s` in reverse, starting at `start` and going - ## backwards to 0. + ## Searches for string `sub` in `s` in reverse, starting at position `start` + ## (default: the last character) and going backwards to the first character. ## ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. + ## + ## See also: + ## * `find proc<#find,string,string,Natural,int>`_ if sub.len == 0: return -1 let realStart = if start == -1: s.len else: start @@ -1538,54 +1858,34 @@ proc rfind*(s, sub: string, start: int = -1): int {.noSideEffect.} = if result != -1: return return -1 -proc rfind*(s: string, sub: char, start: int = -1): int {.noSideEffect, - rtl.} = - ## Searches for `sub` in `s` in reverse starting at position `start`. - ## - ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. - let realStart = if start == -1: s.len-1 else: start - for i in countdown(realStart, 0): - if sub == s[i]: return i - return -1 -proc rfind*(s: string, chars: set[char], start: int = -1): int {.noSideEffect.} = - ## Searches for `chars` in `s` in reverse starting at position `start`. +proc count*(s: string, sub: char): int {.noSideEffect, + rtl, extern: "nsuCountChar".} = + ## Count the occurrences of the character `sub` in the string `s`. ## - ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. - let realStart = if start == -1: s.len-1 else: start - for i in countdown(realStart, 0): - if s[i] in chars: return i - return -1 + ## See also: + ## * `countLines proc<#countLines,string>`_ + for c in s: + if c == sub: inc result -proc center*(s: string, width: int, fillChar: char = ' '): string {. - noSideEffect, rtl, extern: "nsuCenterString".} = - ## Return the contents of `s` centered in a string `width` long using - ## `fillChar` as padding. +proc count*(s: string, subs: set[char]): int {.noSideEffect, + rtl, extern: "nsuCountCharSet".} = + ## Count the occurrences of the group of character `subs` in the string `s`. ## - ## The original string is returned if `width` is less than or equal - ## to `s.len`. - if width <= s.len: return s - result = newString(width) - # Left padding will be one fillChar - # smaller if there are an odd number - # of characters - let - charsLeft = (width - s.len) - leftPadding = charsLeft div 2 - for i in 0 ..< width: - if i >= leftPadding and i < leftPadding + s.len: - # we are where the string should be located - result[i] = s[i-leftPadding] - else: - # we are either before or after where - # the string s should go - result[i] = fillChar + ## See also: + ## * `countLines proc<#countLines,string>`_ + doAssert card(subs) > 0 + for c in s: + if c in subs: inc result proc count*(s: string, sub: string, overlapping: bool = false): int {. noSideEffect, rtl, extern: "nsuCountString".} = ## Count the occurrences of a substring `sub` in the string `s`. ## Overlapping occurrences of `sub` only count when `overlapping` - ## is set to true. + ## is set to true (default: false). + ## + ## See also: + ## * `countLines proc<#countLines,string>`_ doAssert sub.len > 0 var i = 0 while true: @@ -1595,43 +1895,58 @@ proc count*(s: string, sub: string, overlapping: bool = false): int {. else: i += sub.len inc result -proc count*(s: string, sub: char): int {.noSideEffect, - rtl, extern: "nsuCountChar".} = - ## Count the occurrences of the character `sub` in the string `s`. - for c in s: - if c == sub: inc result - -proc count*(s: string, subs: set[char]): int {.noSideEffect, - rtl, extern: "nsuCountCharSet".} = - ## Count the occurrences of the group of character `subs` in the string `s`. - doAssert card(subs) > 0 - for c in s: - if c in subs: inc result - -proc quoteIfContainsWhite*(s: string): string {.deprecated.} = - ## Returns ``'"' & s & '"'`` if `s` contains a space and does not - ## start with a quote, else returns `s`. +proc countLines*(s: string): int {.noSideEffect, + rtl, extern: "nsuCountLines".} = + ## Returns the number of lines in the string `s`. + ## + ## This is the same as ``len(splitLines(s))``, but much more efficient + ## because it doesn't modify the string creating temporal objects. Every + ## `character literal `_ + ## newline combination (CR, LF, CR-LF) is supported. + ## + ## In this context, a line is any string seperated by a newline combination. + ## A line can be an empty string. ## - ## **DEPRECATED** as it was confused for shell quoting function. For this - ## application use `osproc.quoteShell `_. - if find(s, {' ', '\t'}) >= 0 and s[0] != '"': result = '"' & s & '"' - else: result = s + ## See also: + ## * `splitLines proc<#splitLines,string>`_ + runnableExamples: + doAssert countLines("First line\l and second line.") == 2 + result = 1 + var i = 0 + while i < s.len: + case s[i] + of '\c': + if i+1 < s.len and s[i+1] == '\l': inc i + inc result + of '\l': inc result + else: discard + inc i -proc contains*(s: string, c: char): bool {.noSideEffect.} = - ## Same as ``find(s, c) >= 0``. - return find(s, c) >= 0 proc contains*(s, sub: string): bool {.noSideEffect.} = ## Same as ``find(s, sub) >= 0``. + ## + ## See also: + ## * `find proc<#find,string,string,Natural,int>`_ return find(s, sub) >= 0 proc contains*(s: string, chars: set[char]): bool {.noSideEffect.} = ## Same as ``find(s, chars) >= 0``. + ## + ## See also: + ## * `find proc<#find,string,set[char],Natural,int>`_ return find(s, chars) >= 0 proc replace*(s, sub: string, by = ""): string {.noSideEffect, rtl, extern: "nsuReplaceStr".} = ## Replaces `sub` in `s` by the string `by`. + ## + ## See also: + ## * `find proc<#find,string,string,Natural,int>`_ + ## * `replace proc<#replace,string,char,char>`_ for replacing + ## single characters + ## * `replaceWord proc<#replaceWord,string,string,string>`_ + ## * `multiReplace proc<#multiReplace,string,varargs[]>`_ result = "" let subLen = sub.len if subLen == 0: @@ -1669,6 +1984,11 @@ proc replace*(s: string, sub, by: char): string {.noSideEffect, ## Replaces `sub` in `s` by the character `by`. ## ## Optimized version of `replace <#replace,string,string>`_ for characters. + ## + ## See also: + ## * `find proc<#find,string,char,Natural,int>`_ + ## * `replaceWord proc<#replaceWord,string,string,string>`_ + ## * `multiReplace proc<#multiReplace,string,varargs[]>`_ result = newString(s.len) var i = 0 while i < s.len: @@ -1681,7 +2001,7 @@ proc replaceWord*(s, sub: string, by = ""): string {.noSideEffect, ## Replaces `sub` in `s` by the string `by`. ## ## Each occurrence of `sub` has to be surrounded by word boundaries - ## (comparable to ``\\w`` in regular expressions), otherwise it is not + ## (comparable to ``\b`` in regular expressions), otherwise it is not ## replaced. if sub.len == 0: return s const wordChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'} @@ -1711,14 +2031,14 @@ proc multiReplace*(s: string, replacements: varargs[(string, string)]): string { ## Same as replace, but specialized for doing multiple replacements in a single ## pass through the input string. ## - ## multiReplace performs all replacements in a single pass, this means it can be used - ## to swap the occurences of "a" and "b", for instance. + ## `multiReplace` performs all replacements in a single pass, this means it + ## can be used to swap the occurences of "a" and "b", for instance. ## - ## If the resulting string is not longer than the original input string, only a single - ## memory allocation is required. + ## If the resulting string is not longer than the original input string, + ## only a single memory allocation is required. ## - ## The order of the replacements does matter. Earlier replacements are preferred over later - ## replacements in the argument list. + ## The order of the replacements does matter. Earlier replacements are + ## preferred over later replacements in the argument list. result = newStringOfCap(s.len) var i = 0 var fastChk: set[char] = {} @@ -1740,55 +2060,12 @@ proc multiReplace*(s: string, replacements: varargs[(string, string)]): string { add result, s[i] inc(i) -proc delete*(s: var string, first, last: int) {.noSideEffect, - rtl, extern: "nsuDelete".} = - ## Deletes in `s` the characters at position `first` .. `last`. - ## - ## This modifies `s` itself, it does not return a copy. - var i = first - var j = last+1 - var newLen = len(s)-j+i - while i < newLen: - s[i] = s[j] - inc(i) - inc(j) - setLen(s, newLen) - -proc toOct*(x: BiggestInt, len: Positive): string {.noSideEffect, - rtl, extern: "nsuToOct".} = - ## Converts `x` into its octal representation. - ## - ## The resulting string is always `len` characters long. No leading ``0o`` - ## prefix is generated. - var - mask: BiggestInt = 7 - shift: BiggestInt = 0 - assert(len > 0) - result = newString(len) - for j in countdown(len-1, 0): - result[j] = chr(int((x and mask) shr shift) + ord('0')) - shift = shift + 3 - mask = mask shl 3 -proc toBin*(x: BiggestInt, len: Positive): string {.noSideEffect, - rtl, extern: "nsuToBin".} = - ## Converts `x` into its binary representation. - ## - ## The resulting string is always `len` characters long. No leading ``0b`` - ## prefix is generated. - var - mask: BiggestInt = 1 - shift: BiggestInt = 0 - assert(len > 0) - result = newString(len) - for j in countdown(len-1, 0): - result[j] = chr(int((x and mask) shr shift) + ord('0')) - shift = shift + 1 - mask = mask shl 1 proc insertSep*(s: string, sep = '_', digits = 3): string {.noSideEffect, rtl, extern: "nsuInsertSep".} = - ## Inserts the separator `sep` after `digits` digits from right to left. + ## Inserts the separator `sep` after `digits` characters (default: 3) + ## from right to left. ## ## Even though the algorithm works with any string `s`, it is only useful ## if `s` contains a number. @@ -1810,11 +2087,15 @@ proc insertSep*(s: string, sep = '_', digits = 3): string {.noSideEffect, proc escape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, rtl, extern: "nsuEscape".} = - ## Escapes a string `s`. See `system.addEscapedChar `_ - ## for the escaping scheme. + ## Escapes a string `s`. See `system.addEscapedChar + ## `_ for the escaping scheme. ## ## The resulting string is prefixed with `prefix` and suffixed with `suffix`. ## Both may be empty strings. + ## + ## See also: + ## * `unescape proc<#unescape,string,string,string>`_ for the opposite + ## operation result = newStringOfCap(s.len + s.len shr 2) result.add(prefix) for c in items(s): @@ -1832,8 +2113,8 @@ proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, rtl, extern: "nsuUnescape".} = ## Unescapes a string `s`. ## - ## This complements `escape <#escape>`_ as it performs the opposite - ## operations. + ## This complements `escape proc<#escape,string,string,string>`_ + ## as it performs the opposite operations. ## ## If `s` does not begin with ``prefix`` and end with ``suffix`` a ## ValueError exception will be raised. @@ -1879,101 +2160,12 @@ proc validIdentifier*(s: string): bool {.noSideEffect, ## and is followed by any number of characters of the set `IdentChars`. runnableExamples: doAssert "abc_def08".validIdentifier + if s.len > 0 and s[0] in IdentStartChars: for i in 1..s.len-1: if s[i] notin IdentChars: return false return true -{.push warning[Deprecated]: off.} -proc editDistance*(a, b: string): int {.noSideEffect, - rtl, extern: "nsuEditDistance", - deprecated: "use editdistance.editDistanceAscii instead".} = - ## Returns the edit distance between `a` and `b`. - ## - ## This uses the `Levenshtein`:idx: distance algorithm with only a linear - ## memory overhead. - var len1 = a.len - var len2 = b.len - if len1 > len2: - # make `b` the longer string - return editDistance(b, a) - - # strip common prefix: - var s = 0 - while s < len1 and a[s] == b[s]: - inc(s) - dec(len1) - dec(len2) - # strip common suffix: - while len1 > 0 and len2 > 0 and a[s+len1-1] == b[s+len2-1]: - dec(len1) - dec(len2) - # trivial cases: - if len1 == 0: return len2 - if len2 == 0: return len1 - - # another special case: - if len1 == 1: - for j in s..s+len2-1: - if a[s] == b[j]: return len2 - 1 - return len2 - - inc(len1) - inc(len2) - var half = len1 shr 1 - # initalize first row: - #var row = cast[ptr array[0..high(int) div 8, int]](alloc(len2*sizeof(int))) - var row: seq[int] - newSeq(row, len2) - var e = s + len2 - 1 # end marker - for i in 1..len2 - half - 1: row[i] = i - row[0] = len1 - half - 1 - for i in 1 .. len1 - 1: - var char1 = a[i + s - 1] - var char2p: int - var D, x: int - var p: int - if i >= len1 - half: - # skip the upper triangle: - var offset = i - len1 + half - char2p = offset - p = offset - var c3 = row[p] + ord(char1 != b[s + char2p]) - inc(p) - inc(char2p) - x = row[p] + 1 - D = x - if x > c3: x = c3 - row[p] = x - inc(p) - else: - p = 1 - char2p = 0 - D = i - x = i - if i <= half + 1: - # skip the lower triangle: - e = len2 + i - half - 2 - # main: - while p <= e: - dec(D) - var c3 = D + ord(char1 != b[char2p + s]) - inc(char2p) - inc(x) - if x > c3: x = c3 - D = row[p] + 1 - if x > D: x = D - row[p] = x - inc(p) - # lower triangle sentinel: - if i <= half: - dec(D) - var c3 = D + ord(char1 != b[char2p + s]) - inc(x) - if x > c3: x = c3 - row[p] = x - result = row[e] -{.pop.} # floating point formating: when not defined(js): @@ -1981,10 +2173,11 @@ when not defined(js): importc: "sprintf", varargs, noSideEffect.} type - FloatFormatMode* = enum ## the different modes of floating point formating - ffDefault, ## use the shorter floating point notation - ffDecimal, ## use decimal floating point notation - ffScientific ## use scientific notation (using ``e`` character) + FloatFormatMode* = enum + ## the different modes of floating point formating + ffDefault, ## use the shorter floating point notation + ffDecimal, ## use decimal floating point notation + ffScientific ## use scientific notation (using ``e`` character) proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, precision: range[-1..32] = 16; @@ -2000,6 +2193,11 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, ## after the decimal point for Nim's ``biggestFloat`` type. ## ## If ``precision == -1``, it tries to format it nicely. + runnableExamples: + let x = 123.456 + doAssert x.formatBiggestFloat() == "123.4560000000000" + doAssert x.formatBiggestFloat(ffDecimal, 4) == "123.4560" + doAssert x.formatBiggestFloat(ffScientific, 2) == "1.23e+02" when defined(js): var precision = precision if precision == -1: @@ -2079,11 +2277,18 @@ proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, doAssert x.formatFloat() == "123.4560000000000" doAssert x.formatFloat(ffDecimal, 4) == "123.4560" doAssert x.formatFloat(ffScientific, 2) == "1.23e+02" + result = formatBiggestFloat(f, format, precision, decimalSep) proc trimZeros*(x: var string) {.noSideEffect.} = ## Trim trailing zeros from a formatted floating point - ## value (`x`). Modifies the passed value. + ## value `x` (must be declared as ``var``). + ## + ## This modifies `x` itself, it does not return a copy. + runnableExamples: + var x = "123.456000000" + x.trimZeros() + doAssert x == "123.456" var spl: seq[string] if x.contains('.') or x.contains(','): if x.contains('e'): @@ -2113,6 +2318,9 @@ proc formatSize*(bytes: int64, ## ## `includeSpace` can be set to true to include the (SI preferred) space ## between the number and the unit (e.g. 1 KiB). + ## + ## See also: + ## * `strformat module`_ for string interpolation and formatting runnableExamples: doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" doAssert formatSize((2.234*1024*1024).int) == "2.234MiB" @@ -2120,6 +2328,7 @@ proc formatSize*(bytes: int64, doAssert formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB" doAssert formatSize(4096) == "4KiB" doAssert formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB" + const iecPrefixes = ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"] const collPrefixes = ["", "k", "M", "G", "T", "P", "E", "Z", "Y"] var @@ -2215,6 +2424,9 @@ proc formatEng*(f: BiggestFloat, ## formatEng(4100, unit="", useUnitSpace=true) == "4.1e3 " # Space with useUnitSpace=true ## ## `decimalSep` is used as the decimal separator. + ## + ## See also: + ## * `strformat module`_ for string interpolation and formatting var absolute: BiggestFloat significand: BiggestFloat @@ -2402,110 +2614,69 @@ proc `%` *(formatstr: string, a: openArray[string]): string {.noSideEffect, ## ## The variables are compared with `cmpIgnoreStyle`. `ValueError` is ## raised if an ill-formed format string has been passed to the `%` operator. + ## + ## See also: + ## * `strformat module`_ for string interpolation and formatting result = newStringOfCap(formatstr.len + a.len shl 4) addf(result, formatstr, a) proc `%` *(formatstr, a: string): string {.noSideEffect, rtl, extern: "nsuFormatSingleElem".} = - ## This is the same as ``formatstr % [a]``. + ## This is the same as ``formatstr % [a]`` (see + ## `% proc<#%25,string,openArray[string]>`_). result = newStringOfCap(formatstr.len + a.len) addf(result, formatstr, [a]) proc format*(formatstr: string, a: varargs[string, `$`]): string {.noSideEffect, rtl, extern: "nsuFormatVarargs".} = - ## This is the same as ``formatstr % a`` except that it supports + ## This is the same as ``formatstr % a`` (see + ## `% proc<#%25,string,openArray[string]>`_) except that it supports ## auto stringification. + ## + ## See also: + ## * `strformat module`_ for string interpolation and formatting result = newStringOfCap(formatstr.len + a.len) addf(result, formatstr, a) {.pop.} -proc removeSuffix*(s: var string, chars: set[char] = Newlines) {. - rtl, extern: "nsuRemoveSuffixCharSet".} = - ## Removes all characters from `chars` from the end of the string `s` - ## (in-place). - runnableExamples: - var userInput = "Hello World!*~\r\n" - userInput.removeSuffix - doAssert userInput == "Hello World!*~" - userInput.removeSuffix({'~', '*'}) - doAssert userInput == "Hello World!" - var otherInput = "Hello!?!" - otherInput.removeSuffix({'!', '?'}) - doAssert otherInput == "Hello" - if s.len == 0: return - var last = s.high - while last > -1 and s[last] in chars: last -= 1 - s.setLen(last + 1) -proc removeSuffix*(s: var string, c: char) {. - rtl, extern: "nsuRemoveSuffixChar".} = - ## Removes all occurrences of a single character (in-place) from the end - ## of a string. +proc strip*(s: string, leading = true, trailing = true, + chars: set[char] = Whitespace): string + {.noSideEffect, rtl, extern: "nsuStrip".} = + ## Strips leading or trailing `chars` (default: whitespace characters) + ## from `s` and returns the resulting string. + ## + ## If `leading` is true (default), leading `chars` are stripped. + ## If `trailing` is true (default), trailing `chars` are stripped. + ## If both are false, the string is returned unchanged. ## + ## See also: + ## * `stripLineEnd proc<#stripLineEnd,string>`_ runnableExamples: - var table = "users" - table.removeSuffix('s') - doAssert table == "user" + let a = " vhellov " + let b = strip(a) + doAssert b == "vhellov" - var dots = "Trailing dots......." - dots.removeSuffix('.') - doAssert dots == "Trailing dots" - removeSuffix(s, chars = {c}) + doAssert a.strip(leading = false) == " vhellov" + doAssert a.strip(trailing = false) == "vhellov " -proc removeSuffix*(s: var string, suffix: string) {. - rtl, extern: "nsuRemoveSuffixString".} = - ## Remove the first matching suffix (in-place) from a string. - runnableExamples: - var answers = "yeses" - answers.removeSuffix("es") - doAssert answers == "yes" - var newLen = s.len - if s.endsWith(suffix): - newLen -= len(suffix) - s.setLen(newLen) + doAssert b.strip(chars = {'v'}) == "hello" + doAssert b.strip(leading = false, chars = {'v'}) == "vhello" -proc removePrefix*(s: var string, chars: set[char] = Newlines) {. - rtl, extern: "nsuRemovePrefixCharSet".} = - ## Removes all characters from `chars` from the start of the string `s` - ## (in-place). - ## - runnableExamples: - var userInput = "\r\n*~Hello World!" - userInput.removePrefix - doAssert userInput == "*~Hello World!" - userInput.removePrefix({'~', '*'}) - doAssert userInput == "Hello World!" + let c = "blaXbla" + doAssert c.strip(chars = {'b', 'a'}) == "laXbl" + doAssert c.strip(chars = {'b', 'a', 'l'}) == "X" - var otherInput = "?!?Hello!?!" - otherInput.removePrefix({'!', '?'}) - doAssert otherInput == "Hello!?!" - var start = 0 - while start < s.len and s[start] in chars: start += 1 - if start > 0: s.delete(0, start - 1) - -proc removePrefix*(s: var string, c: char) {. - rtl, extern: "nsuRemovePrefixChar".} = - ## Removes all occurrences of a single character (in-place) from the start - ## of a string. - ## - runnableExamples: - var ident = "pControl" - ident.removePrefix('p') - doAssert ident == "Control" - removePrefix(s, chars = {c}) - -proc removePrefix*(s: var string, prefix: string) {. - rtl, extern: "nsuRemovePrefixString".} = - ## Remove the first matching prefix (in-place) from a string. - ## - runnableExamples: - var answers = "yesyes" - answers.removePrefix("yes") - doAssert answers == "yes" - if s.startsWith(prefix): - s.delete(0, prefix.len - 1) + var + first = 0 + last = len(s)-1 + if leading: + while first <= last and s[first] in chars: inc(first) + if trailing: + while last >= 0 and s[last] in chars: dec(last) + result = substr(s, first, last) proc stripLineEnd*(s: var string) = ## Returns ``s`` stripped from one of these suffixes: @@ -2519,6 +2690,7 @@ proc stripLineEnd*(s: var string) = s = "foo\r\n" s.stripLineEnd doAssert s == "foo" + if s.len > 0: case s[^1] of '\n': @@ -2531,6 +2703,331 @@ proc stripLineEnd*(s: var string) = else: discard + +iterator tokenize*(s: string, seps: set[char] = Whitespace): tuple[ + token: string, isSep: bool] = + ## Tokenizes the string `s` into substrings. + ## + ## Substrings are separated by a substring containing only `seps`. + ## Example: + ## + ## .. code-block:: nim + ## for word in tokenize(" this is an example "): + ## writeLine(stdout, word) + ## + ## Results in: + ## + ## .. code-block:: nim + ## (" ", true) + ## ("this", false) + ## (" ", true) + ## ("is", false) + ## (" ", true) + ## ("an", false) + ## (" ", true) + ## ("example", false) + ## (" ", true) + var i = 0 + while true: + var j = i + var isSep = j < s.len and s[j] in seps + while j < s.len and (s[j] in seps) == isSep: inc(j) + if j > i: + yield (substr(s, i, j-1), isSep) + else: + break + i = j + + + + + +# -------------------------------------------------------------------------- +# Deprecated procs + +{.push warning[Deprecated]: off.} +proc editDistance*(a, b: string): int {.noSideEffect, + rtl, extern: "nsuEditDistance", + deprecated: "use editdistance.editDistanceAscii instead".} = + ## **Deprecated**: Use `editdistance module`_ + ## + ## Returns the edit distance between `a` and `b`. + ## + ## This uses the `Levenshtein`:idx: distance algorithm with only a linear + ## memory overhead. + var len1 = a.len + var len2 = b.len + if len1 > len2: + # make `b` the longer string + return editDistance(b, a) + + # strip common prefix: + var s = 0 + while s < len1 and a[s] == b[s]: + inc(s) + dec(len1) + dec(len2) + # strip common suffix: + while len1 > 0 and len2 > 0 and a[s+len1-1] == b[s+len2-1]: + dec(len1) + dec(len2) + # trivial cases: + if len1 == 0: return len2 + if len2 == 0: return len1 + + # another special case: + if len1 == 1: + for j in s..s+len2-1: + if a[s] == b[j]: return len2 - 1 + return len2 + + inc(len1) + inc(len2) + var half = len1 shr 1 + # initalize first row: + #var row = cast[ptr array[0..high(int) div 8, int]](alloc(len2*sizeof(int))) + var row: seq[int] + newSeq(row, len2) + var e = s + len2 - 1 # end marker + for i in 1..len2 - half - 1: row[i] = i + row[0] = len1 - half - 1 + for i in 1 .. len1 - 1: + var char1 = a[i + s - 1] + var char2p: int + var D, x: int + var p: int + if i >= len1 - half: + # skip the upper triangle: + var offset = i - len1 + half + char2p = offset + p = offset + var c3 = row[p] + ord(char1 != b[s + char2p]) + inc(p) + inc(char2p) + x = row[p] + 1 + D = x + if x > c3: x = c3 + row[p] = x + inc(p) + else: + p = 1 + char2p = 0 + D = i + x = i + if i <= half + 1: + # skip the lower triangle: + e = len2 + i - half - 2 + # main: + while p <= e: + dec(D) + var c3 = D + ord(char1 != b[char2p + s]) + inc(char2p) + inc(x) + if x > c3: x = c3 + D = row[p] + 1 + if x > D: x = D + row[p] = x + inc(p) + # lower triangle sentinel: + if i <= half: + dec(D) + var c3 = D + ord(char1 != b[char2p + s]) + inc(x) + if x > c3: x = c3 + row[p] = x + result = row[e] +{.pop.} + +proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, + extern: "nsuIsNilOrEmpty", + deprecated: "use 'x.len == 0' instead".} = + ## **Deprecated**: use 'x.len == 0' + ## + ## Checks if `s` is nil or empty. + result = len(s) == 0 + +proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrWhitespace".} = + ## Checks if `s` is nil or consists entirely of whitespace characters. + result = true + for c in s: + if not c.isSpaceAscii(): + return false + +template isImpl(call) = + if s.len == 0: return false + result = true + for c in s: + if not call(c): return false + +proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsAlphaAsciiStr", + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = + ## **Deprecated**: Deprecated since version 0.20 since its semantics are unclear + ## + ## Checks whether or not `s` is alphabetical. + ## + ## This checks a-z, A-Z ASCII characters only. + ## Returns true if all characters in `s` are + ## alphabetic and there is at least one character + ## in `s`. + ## Use `Unicode module`_ for UTF-8 support. + runnableExamples: + doAssert isAlphaAscii("fooBar") == true + doAssert isAlphaAscii("fooBar1") == false + doAssert isAlphaAscii("foo Bar") == false + isImpl isAlphaAscii + +proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsAlphaNumericStr", + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = + ## **Deprecated**: Deprecated since version 0.20 since its semantics are unclear + ## + ## Checks whether or not `s` is alphanumeric. + ## + ## This checks a-z, A-Z, 0-9 ASCII characters only. + ## Returns true if all characters in `s` are + ## alpanumeric and there is at least one character + ## in `s`. + ## Use `Unicode module`_ for UTF-8 support. + runnableExamples: + doAssert isAlphaNumeric("fooBar") == true + doAssert isAlphaNumeric("fooBar1") == true + doAssert isAlphaNumeric("foo Bar") == false + isImpl isAlphaNumeric + +proc isDigit*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsDigitStr", + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = + ## **Deprecated**: Deprecated since version 0.20 since its semantics are unclear + ## + ## Checks whether or not `s` is a numeric value. + ## + ## This checks 0-9 ASCII characters only. + ## Returns true if all characters in `s` are + ## numeric and there is at least one character + ## in `s`. + runnableExamples: + doAssert isDigit("1908") == true + doAssert isDigit("fooBar1") == false + isImpl isDigit + +proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsSpaceAsciiStr", + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = + ## **Deprecated**: Deprecated since version 0.20 since its semantics are unclear + ## + ## Checks whether or not `s` is completely whitespace. + ## + ## Returns true if all characters in `s` are whitespace + ## characters and there is at least one character in `s`. + runnableExamples: + doAssert isSpaceAscii(" ") == true + doAssert isSpaceAscii("") == false + isImpl isSpaceAscii + +template isCaseImpl(s, charProc, skipNonAlpha) = + var hasAtleastOneAlphaChar = false + if s.len == 0: return false + for c in s: + if skipNonAlpha: + var charIsAlpha = c.isAlphaAscii() + if not hasAtleastOneAlphaChar: + hasAtleastOneAlphaChar = charIsAlpha + if charIsAlpha and (not charProc(c)): + return false + else: + if not charProc(c): + return false + return if skipNonAlpha: hasAtleastOneAlphaChar else: true + +proc isLowerAscii*(s: string, skipNonAlpha: bool): bool {. + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = + ## **Deprecated**: Deprecated since version 0.20 since its semantics are unclear + ## + ## Checks whether ``s`` is lower case. + ## + ## This checks ASCII characters only. + ## + ## If ``skipNonAlpha`` is true, returns true if all alphabetical + ## characters in ``s`` are lower case. Returns false if none of the + ## characters in ``s`` are alphabetical. + ## + ## If ``skipNonAlpha`` is false, returns true only if all characters + ## in ``s`` are alphabetical and lower case. + ## + ## For either value of ``skipNonAlpha``, returns false if ``s`` is + ## an empty string. + ## Use `Unicode module`_ for UTF-8 support. + runnableExamples: + doAssert isLowerAscii("1foobar", false) == false + doAssert isLowerAscii("1foobar", true) == true + doAssert isLowerAscii("1fooBar", true) == false + isCaseImpl(s, isLowerAscii, skipNonAlpha) + +proc isUpperAscii*(s: string, skipNonAlpha: bool): bool {. + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = + ## **Deprecated**: Deprecated since version 0.20 since its semantics are unclear + ## + ## Checks whether ``s`` is upper case. + ## + ## This checks ASCII characters only. + ## + ## If ``skipNonAlpha`` is true, returns true if all alphabetical + ## characters in ``s`` are upper case. Returns false if none of the + ## characters in ``s`` are alphabetical. + ## + ## If ``skipNonAlpha`` is false, returns true only if all characters + ## in ``s`` are alphabetical and upper case. + ## + ## For either value of ``skipNonAlpha``, returns false if ``s`` is + ## an empty string. + ## Use `Unicode module`_ for UTF-8 support. + runnableExamples: + doAssert isUpperAscii("1FOO", false) == false + doAssert isUpperAscii("1FOO", true) == true + doAssert isUpperAscii("1Foo", true) == false + isCaseImpl(s, isUpperAscii, skipNonAlpha) + +proc wordWrap*(s: string, maxLineWidth = 80, + splitLongWords = true, + seps: set[char] = Whitespace, + newLine = "\n"): string {. + noSideEffect, rtl, extern: "nsuWordWrap", + deprecated: "use wrapWords in std/wordwrap instead".} = + ## **Deprecated**: use wrapWords in std/wordwrap instead + ## + ## Word wraps `s`. + result = newStringOfCap(s.len + s.len shr 6) + var spaceLeft = maxLineWidth + var lastSep = "" + for word, isSep in tokenize(s, seps): + if isSep: + lastSep = word + spaceLeft = spaceLeft - len(word) + continue + if len(word) > spaceLeft: + if splitLongWords and len(word) > maxLineWidth: + result.add(substr(word, 0, spaceLeft-1)) + var w = spaceLeft + var wordLeft = len(word) - spaceLeft + while wordLeft > 0: + result.add(newLine) + var L = min(maxLineWidth, wordLeft) + spaceLeft = maxLineWidth - L + result.add(substr(word, w, w+L-1)) + inc(w, L) + dec(wordLeft, L) + else: + spaceLeft = maxLineWidth - len(word) + result.add(newLine) + result.add(word) + else: + spaceLeft = spaceLeft - len(word) + result.add(lastSep & word) + lastSep.setLen(0) + + + when isMainModule: proc nonStaticTests = doAssert formatBiggestFloat(1234.567, ffDecimal, -1) == "1234.567000" diff --git a/tests/errmsgs/tunknown_named_parameter.nim b/tests/errmsgs/tunknown_named_parameter.nim index b6b855136..8a3bcaf03 100644 --- a/tests/errmsgs/tunknown_named_parameter.nim +++ b/tests/errmsgs/tunknown_named_parameter.nim @@ -2,10 +2,6 @@ discard """ cmd: "nim check $file" errormsg: "type mismatch: got " nimout: ''' -proc rsplit(s: string; sep: string; maxsplit: int = -1): seq[string] - first type mismatch at position: 2 - required type: string - but expression '{':'}' is of type: set[char] proc rsplit(s: string; sep: char; maxsplit: int = -1): seq[string] first type mismatch at position: 2 required type: char @@ -13,6 +9,10 @@ proc rsplit(s: string; sep: char; maxsplit: int = -1): seq[string] proc rsplit(s: string; seps: set[char] = Whitespace; maxsplit: int = -1): seq[string] first type mismatch at position: 3 unknown named parameter: maxsplits +proc rsplit(s: string; sep: string; maxsplit: int = -1): seq[string] + first type mismatch at position: 2 + required type: string + but expression '{':'}' is of type: set[char] expression: rsplit("abc:def", {':'}, maxsplits = 1) ''' -- cgit 1.4.1-2-gfad0 From b40a637fac1334339c260951d0dd3fa3682de476 Mon Sep 17 00:00:00 2001 From: narimiran Date: Fri, 11 Jan 2019 13:22:10 +0100 Subject: better docs: tables --- lib/pure/collections/tableimpl.nim | 2 +- lib/pure/collections/tables.nim | 2658 +++++++++++++++++++++++++++--------- 2 files changed, 2034 insertions(+), 626 deletions(-) diff --git a/lib/pure/collections/tableimpl.nim b/lib/pure/collections/tableimpl.nim index 9a5bffcef..2cdc62996 100644 --- a/lib/pure/collections/tableimpl.nim +++ b/lib/pure/collections/tableimpl.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -## An ``include`` file for the different table implementations. +# An ``include`` file for the different table implementations. # hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These # two procs retain clarity of that encoding without the space cost of an enum. diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 1fa2ca0a6..659b4473b 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -7,18 +7,25 @@ # distribution, for details about the copyright. # +## :Author: Nim contributors +## ## The ``tables`` module implements variants of an efficient `hash table`:idx: ## (also often named `dictionary`:idx: in other programming languages) that is -## a mapping from keys to values. ``Table`` is the usual hash table, -## ``OrderedTable`` is like ``Table`` but remembers insertion order -## and ``CountTable`` is a mapping from a key to its number of occurrences. +## a mapping from keys to values. +## +## There are several different types of hash tables available: +## * `Table<#Table>`_ is the usual hash table, +## * `OrderedTable<#OrderedTable>`_ is like ``Table`` but remembers insertion order, +## * `CountTable<#CountTable>`_ is a mapping from a key to its number of occurrences ## ## For consistency with every other data type in Nim these have **value** ## semantics, this means that ``=`` performs a copy of the hash table. -## For **reference** semantics use the ``Ref`` variant: ``TableRef``, -## ``OrderedTableRef``, ``CountTableRef``. ## -## To give an example, when ``a`` is a Table, then ``var b = a`` gives ``b`` +## For `ref semantics`_ +## use their ``Ref`` variants: `TableRef<#TableRef>`_, +## `OrderedTableRef<#OrderedTableRef>`_, and `CountTableRef<#CountTableRef>`_. +## +## To give an example, when ``a`` is a ``Table``, then ``var b = a`` gives ``b`` ## as a new independent table. ``b`` is initialised with the contents of ``a``. ## Changing ``b`` does not affect ``a`` and vice versa: ## @@ -35,8 +42,8 @@ ## echo a, b # output: {1: one, 2: two}{1: one, 2: two, 3: three} ## echo a == b # output: false ## -## On the other hand, when ``a`` is a TableRef instead, then changes to ``b`` -## also affect ``a``. Both ``a`` and ``b`` reference the same data structure: +## On the other hand, when ``a`` is a ``TableRef`` instead, then changes to ``b`` +## also affect ``a``. Both ``a`` and ``b`` **ref** the same data structure: ## ## .. code-block:: ## import tables @@ -51,27 +58,111 @@ ## echo a, b # output: {1: one, 2: two, 3: three}{1: one, 2: two, 3: three} ## echo a == b # output: true ## +## ---- +## +## Basic usage +## =========== +## +## Table +## ----- +## +## .. code-block:: +## import tables +## from sequtils import zip +## +## let +## names = ["John", "Paul", "George", "Ringo"] +## years = [1940, 1942, 1943, 1940] +## +## var beatles = initTable[string, int]() +## +## for pairs in zip(names, years): +## let (name, birthYear) = pairs +## beatles[name] = birthYear +## +## echo beatles +## # {"George": 1943, "Ringo": 1940, "Paul": 1942, "John": 1940} +## +## +## var beatlesByYear = initTable[int, seq[string]]() +## +## for pairs in zip(years, names): +## let (birthYear, name) = pairs +## if not beatlesByYear.hasKey(birthYear): +## # if a key doesn't exists, we create one with an empty sequence +## # before we can add elements to it +## beatlesByYear[birthYear] = @[] +## beatlesByYear[birthYear].add(name) +## +## echo beatlesByYear +## # {1940: @["John", "Ringo"], 1942: @["Paul"], 1943: @["George"]} ## -## Here is an example of ``CountTable`` usage: ## -## .. code-block:: nim +## +## OrderedTable +## ------------ +## +## `OrderedTable<#OrderedTable>`_ is used when it is important to preserve +## the insertion order of keys. +## +## .. code-block:: +## import tables +## +## let +## a = [('z', 1), ('y', 2), ('x', 3)] +## t = a.toTable # regular table +## ot = a.toOrderedTable # ordered tables +## +## echo t # {'x': 3, 'y': 2, 'z': 1} +## echo ot # {'z': 1, 'y': 2, 'x': 3} +## +## +## +## CountTable +## ---------- +## +## `CountTable<#CountTable>`_ is useful for counting number of items of some +## container (e.g. string, sequence or array), as it is a mapping where the +## items are the keys, and their number of occurrences are the values. +## For that purpose `toCountTable proc<#toCountTable,openArray[A]>`_ +## comes handy: +## +## .. code-block:: +## import tables +## ## let myString = "abracadabra" -## var myTable = initCountTable[char]() +## let letterFrequencies = toCountTable(myString) +## echo letterFrequencies +## # 'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2} +## +## The same could have been achieved by manually iterating over a container +## and increasing each key's value with `inc proc<#inc,CountTable[A],A,int>`_: ## +## .. code-block:: +## import tables +## +## let myString = "abracadabra" +## var letterFrequencies = initCountTable[char]() ## for c in myString: -## myTable.inc(c) +## letterFrequencies.inc(c) +## echo letterFrequencies +## # output: {'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2} ## -## echo myTable # output: {'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2} +## ---- ## ## +## +## Hashing +## ------- +## ## If you are using simple standard types like ``int`` or ``string`` for the ## keys of the table you won't have any problems, but as soon as you try to use ## a more complex object as a key you will be greeted by a strange compiler -## error:: +## error: ## ## Error: type mismatch: got (Person) ## but expected one of: -## hashes.hash(x: openarray[A]): Hash +## hashes.hash(x: openArray[A]): Hash ## hashes.hash(x: int): Hash ## hashes.hash(x: float): Hash ## … @@ -89,6 +180,8 @@ ## example implementing only ``hash`` suffices: ## ## .. code-block:: +## import tables, hashes +## ## type ## Person = object ## firstName, lastName: string @@ -111,45 +204,50 @@ ## p2.firstName = "소진" ## p2.lastName = "박" ## salaries[p2] = 45_000 +## +## ---- +## +## See also +## ======== +## +## * `json module`_ for table-like structure which allows +## heterogeneous members +## * `sharedtables module`_ for shared hash table support +## * `strtabs module`_ for efficient hash tables +## mapping from strings to strings +## * `hashes module`_ for helper functions for hashing -import - hashes, math + +import hashes, math include "system/inclrtl" type KeyValuePair[A, B] = tuple[hcode: Hash, key: A, val: B] KeyValuePairSeq[A, B] = seq[KeyValuePair[A, B]] - Table*[A, B] = object ## generic hash table + Table*[A, B] = object + ## Generic hash table, consisting of a key-value pair. + ## + ## `data` and `counter` are internal implementation details which + ## can't be accessed. + ## + ## For creating an empty Table, use `initTable proc<#initTable,int>`_. data: KeyValuePairSeq[A, B] counter: int - TableRef*[A,B] = ref Table[A, B] + TableRef*[A,B] = ref Table[A, B] ## Ref version of `Table<#Table>`_. + ## + ## For creating a new empty TableRef, use `newTable proc + ## <#newTable,int>`_. + + +# ------------------------------ helpers --------------------------------- template maxHash(t): untyped = high(t.data) template dataLen(t): untyped = len(t.data) include tableimpl -proc clear*[A, B](t: var Table[A, B]) = - ## resets the table so that it is empty. - clearImpl() - -proc clear*[A, B](t: TableRef[A, B]) = - ## resets the ref table so that it is empty. - clearImpl() - -proc rightSize*(count: Natural): int {.inline.} = - ## return the value of ``initialSize`` to support ``count`` items. - ## - ## If more items are expected to be added, simply add that - ## expected extra amount to the parameter before calling this. - ## - ## Internally, we want mustRehash(rightSize(x), x) == false. - result = nextPowerOfTwo(count * 3 div 2 + 4) - -proc len*[A, B](t: Table[A, B]): int = - ## returns the number of keys in ``t``. - result = t.counter +proc rightSize*(count: Natural): int {.inline.} template get(t, key): untyped = ## retrieves the value at ``t[key]``. The value can be modified. @@ -176,36 +274,340 @@ template getOrDefaultImpl(t, key, default: untyped): untyped = var index = rawGet(t, key, hc) result = if index >= 0: t.data[index].val else: default -proc `[]`*[A, B](t: Table[A, B], key: A): B {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. If ``key`` is not in ``t``, the - ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether - ## the key exists. - get(t, key) +template dollarImpl(): untyped {.dirty.} = + if t.len == 0: + result = "{:}" + else: + result = "{" + for key, val in pairs(t): + if result.len > 1: result.add(", ") + result.addQuoted(key) + result.add(": ") + result.addQuoted(val) + result.add("}") -proc `[]`*[A, B](t: var Table[A, B], key: A): var B {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. The value can be modified. +proc enlarge[A, B](t: var Table[A, B]) = + var n: KeyValuePairSeq[A, B] + newSeq(n, len(t.data) * growthFactor) + swap(t.data, n) + for i in countup(0, high(n)): + let eh = n[i].hcode + if isFilled(eh): + var j: Hash = eh and maxHash(t) + while isFilled(t.data[j].hcode): + j = nextTry(j, maxHash(t)) + rawInsert(t, t.data, n[i].key, n[i].val, eh, j) + +template equalsImpl(s, t: typed): typed = + if s.counter == t.counter: + # different insertion orders mean different 'data' seqs, so we have + # to use the slow route here: + for key, val in s: + if not t.hasKey(key): return false + if t.getOrDefault(key) != val: return false + return true + + + +# ------------------------------------------------------------------- +# ------------------------------ Table ------------------------------ +# ------------------------------------------------------------------- + +proc initTable*[A, B](initialSize=64): Table[A, B] = + ## Creates a new hash table that is empty. + ## + ## ``initialSize`` must be a power of two (default: 64). + ## If you need to accept runtime values for this you could use the + ## `nextPowerOfTwo proc`_ from the + ## `math module`_ or the `rightSize proc<#rightSize,Natural>`_ + ## from this module. + ## + ## See also: + ## * `toTable proc<#toTable,openArray[]>`_ + ## * `newTable proc<#newTable,int>`_ for creating a `TableRef` + runnableExamples: + let + a = initTable[int, string]() + b = initTable[char, seq[int]]() + assert isPowerOfTwo(initialSize) + result.counter = 0 + newSeq(result.data, initialSize) + +proc toTable*[A, B](pairs: openArray[(A, B)]): Table[A, B] = + ## Creates a new hash table that contains the given ``pairs``. + ## + ## ``pairs`` is a container consisting of ``(key, value)`` tuples. + ## + ## See also: + ## * `initTable proc<#initTable,int>`_ + ## * `newTable proc<#newTable,openArray[]>`_ for a `TableRef` version + runnableExamples: + let a = [('a', 5), ('b', 9)] + let b = toTable(a) + assert b == {'a': 5, 'b': 9}.toTable + result = initTable[A, B](rightSize(pairs.len)) + for key, val in items(pairs): result[key] = val + +proc `[]`*[A, B](t: Table[A, B], key: A): B = + ## Retrieves the value at ``t[key]``. + ## ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + ## One can check with `hasKey proc<#hasKey,Table[A,B],A>`_ whether + ## the key exists. + ## + ## See also: + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + ## * `[]= proc<#[]=,Table[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,Table[A,B],A>`_ for checking if a key is in + ## the table + runnableExamples: + let a = {'a': 5, 'b': 9}.toTable + doAssert a['a'] == 5 + doAssertRaises(KeyError): + echo a['z'] get(t, key) -proc mget*[A, B](t: var Table[A, B], key: A): var B {.deprecated.} = - ## retrieves the value at ``t[key]``. The value can be modified. +proc `[]`*[A, B](t: var Table[A, B], key: A): var B = + ## Retrieves the value at ``t[key]``. The value can be modified. + ## ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. + ## + ## See also: + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + ## * `[]= proc<#[]=,Table[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,Table[A,B],A>`_ for checking if a key is in + ## the table get(t, key) +proc `[]=`*[A, B](t: var Table[A, B], key: A, val: B) = + ## Inserts a ``(key, value)`` pair into ``t``. + ## + ## See also: + ## * `[] proc<#[],Table[A,B],A>`_ for retrieving a value of a key + ## * `hasKeyOrPut proc<#hasKeyOrPut,Table[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,Table[A,B],A,B>`_ + ## * `del proc<#del,Table[A,B],A>`_ for removing a key from the table + runnableExamples: + var a = initTable[char, int]() + a['x'] = 7 + a['y'] = 33 + doAssert a == {'x': 7, 'y': 33}.toTable + putImpl(enlarge) + +proc hasKey*[A, B](t: Table[A, B], key: A): bool = + ## Returns true if ``key`` is in the table ``t``. + ## + ## See also: + ## * `contains proc<#contains,Table[A,B],A>`_ for use with the `in` operator + ## * `[] proc<#[],Table[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + let a = {'a': 5, 'b': 9}.toTable + doAssert a.hasKey('a') == true + doAssert a.hasKey('z') == false + var hc: Hash + result = rawGet(t, key, hc) >= 0 + +proc contains*[A, B](t: Table[A, B], key: A): bool = + ## Alias of `hasKey proc<#hasKey,Table[A,B],A>`_ for use with + ## the ``in`` operator. + runnableExamples: + let a = {'a': 5, 'b': 9}.toTable + doAssert 'b' in a == true + doAssert a.contains('z') == false + return hasKey[A, B](t, key) + +proc hasKeyOrPut*[A, B](t: var Table[A, B], key: A, val: B): bool = + ## Returns true if ``key`` is in the table, otherwise inserts ``value``. + ## + ## See also: + ## * `hasKey proc<#hasKey,Table[A,B],A>`_ + ## * `[] proc<#[],Table[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.toTable + if a.hasKeyOrPut('a', 50): + a['a'] = 99 + if a.hasKeyOrPut('z', 50): + a['z'] = 99 + doAssert a == {'a': 99, 'b': 9, 'z': 50}.toTable + hasKeyOrPutImpl(enlarge) + proc getOrDefault*[A, B](t: Table[A, B], key: A): B = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, the + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. Otherwise, the ## default initialization value for type ``B`` is returned (e.g. 0 for any ## integer type). + ## + ## See also: + ## * `[] proc<#[],Table[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,Table[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,Table[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,Table[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + let a = {'a': 5, 'b': 9}.toTable + doAssert a.getOrDefault('a') == 5 + doAssert a.getOrDefault('z') == 0 getOrDefaultImpl(t, key) proc getOrDefault*[A, B](t: Table[A, B], key: A, default: B): B = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. ## Otherwise, ``default`` is returned. + ## + ## See also: + ## * `[] proc<#[],Table[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,Table[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,Table[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,Table[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + runnableExamples: + let a = {'a': 5, 'b': 9}.toTable + doAssert a.getOrDefault('a', 99) == 5 + doAssert a.getOrDefault('z', 99) == 99 getOrDefaultImpl(t, key, default) +proc mgetOrPut*[A, B](t: var Table[A, B], key: A, val: B): var B = + ## Retrieves value at ``t[key]`` or puts ``val`` if not present, either way + ## returning a value which can be modified. + ## + ## See also: + ## * `[] proc<#[],Table[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,Table[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,Table[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.toTable + doAssert a.mgetOrPut('a', 99) == 5 + doAssert a.mgetOrPut('z', 99) == 99 + doAssert a == {'a': 5, 'b': 9, 'z': 99}.toTable + mgetOrPutImpl(enlarge) + +proc len*[A, B](t: Table[A, B]): int = + ## Returns the number of keys in ``t``. + runnableExamples: + let a = {'a': 5, 'b': 9}.toTable + doAssert len(a) == 2 + result = t.counter + +proc add*[A, B](t: var Table[A, B], key: A, val: B) = + ## Puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. + ## + ## **This can introduce duplicate keys into the table!** + ## + ## Use `[]= proc<#[]=,Table[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table without introducing duplicates. + addImpl(enlarge) + +proc del*[A, B](t: var Table[A, B], key: A) = + ## Deletes ``key`` from hash table ``t``. Does nothing if the key does not exist. + ## + ## See also: + ## * `take proc<#take,Table[A,B],A,B>`_ + ## * `clear proc<#clear,Table[A,B]>`_ to empty the whole table + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.toTable + a.del('a') + doAssert a == {'b': 9, 'c': 13}.toTable + a.del('z') + doAssert a == {'b': 9, 'c': 13}.toTable + delImpl() + +proc take*[A, B](t: var Table[A, B], key: A, val: var B): bool = + ## Deletes the ``key`` from the table. + ## Returns ``true``, if the ``key`` existed, and sets ``val`` to the + ## mapping of the key. Otherwise, returns ``false``, and the ``val`` is + ## unchanged. + ## + ## See also: + ## * `del proc<#del,Table[A,B],A>`_ + ## * `clear proc<#clear,Table[A,B]>`_ to empty the whole table + runnableExamples: + var + a = {'a': 5, 'b': 9, 'c': 13}.toTable + i: int + doAssert a.take('b', i) == true + doAssert a == {'a': 5, 'c': 13}.toTable + doAssert i == 9 + i = 0 + doAssert a.take('z', i) == false + doAssert a == {'a': 5, 'c': 13}.toTable + doAssert i == 0 + + var hc: Hash + var index = rawGet(t, key, hc) + result = index >= 0 + if result: + shallowCopy(val, t.data[index].val) + delImplIdx(t, index) + +proc clear*[A, B](t: var Table[A, B]) = + ## Resets the table so that it is empty. + ## + ## See also: + ## * `del proc<#del,Table[A,B],A>`_ + ## * `take proc<#take,Table[A,B],A,B>`_ + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.toTable + doAssert len(a) == 3 + clear(a) + doAssert len(a) == 0 + clearImpl() + +proc `$`*[A, B](t: Table[A, B]): string = + ## The ``$`` operator for hash tables. Used internally when calling `echo` + ## on a table. + dollarImpl() + +proc `==`*[A, B](s, t: Table[A, B]): bool = + ## The ``==`` operator for hash tables. Returns ``true`` if the content of both + ## tables contains the same key-value pairs. Insert order does not matter. + runnableExamples: + let + a = {'a': 5, 'b': 9, 'c': 13}.toTable + b = {'b': 9, 'c': 13, 'a': 5}.toTable + doAssert a == b + equalsImpl(s, t) + +proc rightSize*(count: Natural): int {.inline.} = + ## Return the value of ``initialSize`` to support ``count`` items. + ## + ## If more items are expected to be added, simply add that + ## expected extra amount to the parameter before calling this. + ## + ## Internally, we want mustRehash(rightSize(x), x) == false. + result = nextPowerOfTwo(count * 3 div 2 + 4) + +proc indexBy*[A, B, C](collection: A, index: proc(x: B): C): Table[C, B] = + ## Index the collection with the proc provided. + # TODO: As soon as supported, change collection: A to collection: A[B] + result = initTable[C, B]() + for item in collection: + result[index(item)] = item + + + template withValue*[A, B](t: var Table[A, B], key: A, value, body: untyped) = - ## retrieves the value at ``t[key]``. + ## Retrieves the value at ``t[key]``. + ## ## ``value`` can be modified in the scope of the ``withValue`` call. ## ## .. code-block:: nim @@ -225,7 +627,8 @@ template withValue*[A, B](t: var Table[A, B], key: A, value, body: untyped) = template withValue*[A, B](t: var Table[A, B], key: A, value, body1, body2: untyped) = - ## retrieves the value at ``t[key]``. + ## Retrieves the value at ``t[key]``. + ## ## ``value`` can be modified in the scope of the ``withValue`` call. ## ## .. code-block:: nim @@ -248,370 +651,535 @@ template withValue*[A, B](t: var Table[A, B], key: A, else: body2 -iterator allValues*[A, B](t: Table[A, B]; key: A): B = - ## iterates over any value in the table ``t`` that belongs to the given ``key``. - var h: Hash = genHash(key) and high(t.data) - while isFilled(t.data[h].hcode): - if t.data[h].key == key: - yield t.data[h].val - h = nextTry(h, high(t.data)) - -proc hasKey*[A, B](t: Table[A, B], key: A): bool = - ## returns true iff ``key`` is in the table ``t``. - var hc: Hash - result = rawGet(t, key, hc) >= 0 - -proc contains*[A, B](t: Table[A, B], key: A): bool = - ## alias of ``hasKey`` for use with the ``in`` operator. - return hasKey[A, B](t, key) iterator pairs*[A, B](t: Table[A, B]): (A, B) = - ## iterates over any ``(key, value)`` pair in the table ``t``. + ## Iterates over any ``(key, value)`` pair in the table ``t``. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,Table[A,B]>`_ + ## * `keys iterator<#keys.i,Table[A,B]>`_ + ## * `values iterator<#values.i,Table[A,B]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## let a = { + ## 'o': [1, 5, 7, 9], + ## 'e': [2, 4, 6, 8] + ## }.toTable + ## + ## for k, v in a.pairs: + ## echo "key: ", k + ## echo "value: ", v + ## + ## # key: e + ## # value: [2, 4, 6, 8] + ## # key: o + ## # value: [1, 5, 7, 9] for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) iterator mpairs*[A, B](t: var Table[A, B]): (A, var B) = - ## iterates over any ``(key, value)`` pair in the table ``t``. The values - ## can be modified. + ## Iterates over any ``(key, value)`` pair in the table ``t`` (must be + ## declared as `var`). The values can be modified. + ## + ## See also: + ## * `pairs iterator<#pairs.i,Table[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,Table[A,B]>`_ + runnableExamples: + var a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toTable + for k, v in a.mpairs: + v.add(v[0] + 10) + doAssert a == {'e': @[2, 4, 6, 8, 12], 'o': @[1, 5, 7, 9, 11]}.toTable for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) iterator keys*[A, B](t: Table[A, B]): A = - ## iterates over any key in the table ``t``. + ## Iterates over any key in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,Table[A,B]>`_ + ## * `values iterator<#values.i,Table[A,B]>`_ + runnableExamples: + var a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toTable + for k in a.keys: + a[k].add(99) + doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.toTable for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield t.data[h].key iterator values*[A, B](t: Table[A, B]): B = - ## iterates over any value in the table ``t``. + ## Iterates over any value in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,Table[A,B]>`_ + ## * `keys iterator<#keys.i,Table[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,Table[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toTable + for v in a.values: + doAssert v.len == 4 for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield t.data[h].val iterator mvalues*[A, B](t: var Table[A, B]): var B = - ## iterates over any value in the table ``t``. The values can be modified. + ## Iterates over any value in the table ``t`` (must be + ## declared as `var`). The values can be modified. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,Table[A,B]>`_ + ## * `values iterator<#values.i,Table[A,B]>`_ + runnableExamples: + var a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toTable + for v in a.mvalues: + v.add(99) + doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.toTable for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield t.data[h].val -proc del*[A, B](t: var Table[A, B], key: A) = - ## deletes ``key`` from hash table ``t``. Does nothing if the key does not exist. - delImpl() +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``. + ## + ## Used if you have a table with duplicate keys (as a result of using + ## `add proc<#add,Table[A,B],A,B>`_). + ## + ## **Examples:** + ## + ## .. code-block:: + ## var a = {'a': 3, 'b': 5}.toTable + ## for i in 1..3: + ## a.add('z', 10*i) + ## echo a # {'a': 3, 'b': 5, 'z': 10, 'z': 20, 'z': 30} + ## + ## for v in a.allValues('z'): + ## echo v + ## # 10 + ## # 20 + ## # 30 + 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 + h = nextTry(h, high(t.data)) -proc take*[A, B](t: var Table[A, B], key: A, val: var B): bool = - ## deletes the ``key`` from the table. - ## Returns ``true``, if the ``key`` existed, and sets ``val`` to the - ## mapping of the key. Otherwise, returns ``false``, and the ``val`` is - ## unchanged. - var hc: Hash - var index = rawGet(t, key, hc) - result = index >= 0 - if result: - shallowCopy(val, t.data[index].val) - delImplIdx(t, index) -proc enlarge[A, B](t: var Table[A, B]) = - var n: KeyValuePairSeq[A, B] - newSeq(n, len(t.data) * growthFactor) - swap(t.data, n) - for i in countup(0, high(n)): - let eh = n[i].hcode - if isFilled(eh): - var j: Hash = eh and maxHash(t) - while isFilled(t.data[j].hcode): - j = nextTry(j, maxHash(t)) - rawInsert(t, t.data, n[i].key, n[i].val, eh, j) - -proc mgetOrPut*[A, B](t: var Table[A, B], key: A, val: B): var B = - ## retrieves value at ``t[key]`` or puts ``val`` if not present, either way - ## returning a value which can be modified. - mgetOrPutImpl(enlarge) - -proc hasKeyOrPut*[A, B](t: var Table[A, B], key: A, val: B): bool = - ## returns true iff ``key`` is in the table, otherwise inserts ``value``. - hasKeyOrPutImpl(enlarge) -proc `[]=`*[A, B](t: var Table[A, B], key: A, val: B) = - ## puts a ``(key, value)`` pair into ``t``. - putImpl(enlarge) - -proc add*[A, B](t: var Table[A, B], key: A, val: B) = - ## puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. - ## This can introduce duplicate keys into the table! - addImpl(enlarge) +# ------------------------------------------------------------------- +# ---------------------------- TableRef ----------------------------- +# ------------------------------------------------------------------- -proc len*[A, B](t: TableRef[A, B]): int = - ## returns the number of keys in ``t``. - result = t.counter -proc initTable*[A, B](initialSize=64): Table[A, B] = - ## creates a new hash table that is empty. +proc newTable*[A, B](initialSize=64): TableRef[A, B] = + ## Creates a new ref hash table that is empty. ## - ## ``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 or the ``rightSize`` proc from this module. - assert isPowerOfTwo(initialSize) - result.counter = 0 - newSeq(result.data, initialSize) - -proc toTable*[A, B](pairs: openArray[(A, B)]): Table[A, B] = - ## creates a new hash table that contains the given ``pairs``. - result = initTable[A, B](rightSize(pairs.len)) - for key, val in items(pairs): result[key] = val - -template dollarImpl(): untyped {.dirty.} = - if t.len == 0: - result = "{:}" - else: - result = "{" - for key, val in pairs(t): - if result.len > 1: result.add(", ") - result.addQuoted(key) - result.add(": ") - result.addQuoted(val) - result.add("}") - -proc `$`*[A, B](t: Table[A, B]): string = - ## the ``$`` operator for hash tables. - dollarImpl() - -proc hasKey*[A, B](t: TableRef[A, B], key: A): bool = - ## returns true iff ``key`` is in the table ``t``. - result = t[].hasKey(key) - -template equalsImpl(s, t: typed): typed = - if s.counter == t.counter: - # different insertion orders mean different 'data' seqs, so we have - # to use the slow route here: - for key, val in s: - if not t.hasKey(key): return false - if t.getOrDefault(key) != val: return false - return true + ## ``initialSize`` must be a power of two (default: 64). + ## If you need to accept runtime values for this you could use the + ## `nextPowerOfTwo proc`_ from the + ## `math module`_ or the `rightSize proc<#rightSize,Natural>`_ + ## from this module. + ## + ## See also: + ## * `newTable proc<#newTable,openArray[]>`_ for creating a `TableRef` + ## from a collection of `(key, value)` pairs + ## * `initTable proc<#initTable,int>`_ for creating a `Table` + runnableExamples: + let + a = newTable[int, string]() + b = newTable[char, seq[int]]() + new(result) + result[] = initTable[A, B](initialSize) -proc `==`*[A, B](s, t: Table[A, B]): bool = - ## The ``==`` operator for hash tables. Returns ``true`` iff the content of both - ## tables contains the same key-value pairs. Insert order does not matter. - equalsImpl(s, t) +proc newTable*[A, B](pairs: openArray[(A, B)]): TableRef[A, B] = + ## Creates a new ref hash table that contains the given ``pairs``. + ## + ## ``pairs`` is a container consisting of ``(key, value)`` tuples. + ## + ## See also: + ## * `newTable proc<#newTable,int>`_ + ## * `toTable proc<#toTable,openArray[]>`_ for a `Table` version + runnableExamples: + let a = [('a', 5), ('b', 9)] + let b = newTable(a) + assert b == {'a': 5, 'b': 9}.newTable + new(result) + result[] = toTable[A, B](pairs) -proc indexBy*[A, B, C](collection: A, index: proc(x: B): C): Table[C, B] = +proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): TableRef[C, B] = ## Index the collection with the proc provided. # TODO: As soon as supported, change collection: A to collection: A[B] - result = initTable[C, B]() + result = newTable[C, B]() for item in collection: result[index(item)] = item -iterator pairs*[A, B](t: TableRef[A, B]): (A, B) = - ## iterates over any ``(key, value)`` pair in the table ``t``. - for h in 0..high(t.data): - if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) - -iterator mpairs*[A, B](t: TableRef[A, B]): (A, var B) = - ## iterates over any ``(key, value)`` pair in the table ``t``. The values - ## can be modified. - for h in 0..high(t.data): - if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) - -iterator keys*[A, B](t: TableRef[A, B]): A = - ## iterates over any key in the table ``t``. - for h in 0..high(t.data): - if isFilled(t.data[h].hcode): yield t.data[h].key +proc `[]`*[A, B](t: TableRef[A, B], key: A): var B = + ## Retrieves the value at ``t[key]``. + ## + ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + ## One can check with `hasKey proc<#hasKey,TableRef[A,B],A>`_ whether + ## the key exists. + ## + ## See also: + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + ## * `[]= proc<#[]=,TableRef[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,TableRef[A,B],A>`_ for checking if a key is in + ## the table + runnableExamples: + let a = {'a': 5, 'b': 9}.newTable + doAssert a['a'] == 5 + doAssertRaises(KeyError): + echo a['z'] + result = t[][key] -iterator values*[A, B](t: TableRef[A, B]): B = - ## iterates over any value in the table ``t``. - for h in 0..high(t.data): - if isFilled(t.data[h].hcode): yield t.data[h].val +proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) = + ## Inserts a ``(key, value)`` pair into ``t``. + ## + ## See also: + ## * `[] proc<#[],TableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKeyOrPut proc<#hasKeyOrPut,TableRef[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,TableRef[A,B],A,B>`_ + ## * `del proc<#del,TableRef[A,B],A>`_ for removing a key from the table + runnableExamples: + var a = newTable[char, int]() + a['x'] = 7 + a['y'] = 33 + doAssert a == {'x': 7, 'y': 33}.newTable + t[][key] = val -iterator mvalues*[A, B](t: TableRef[A, B]): var B = - ## iterates over any value in the table ``t``. The values can be modified. - for h in 0..high(t.data): - if isFilled(t.data[h].hcode): yield t.data[h].val +proc hasKey*[A, B](t: TableRef[A, B], key: A): bool = + ## Returns true if ``key`` is in the table ``t``. + ## + ## See also: + ## * `contains proc<#contains,TableRef[A,B],A>`_ for use with the `in` + ## operator + ## * `[] proc<#[],TableRef[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + let a = {'a': 5, 'b': 9}.newTable + doAssert a.hasKey('a') == true + doAssert a.hasKey('z') == false + result = t[].hasKey(key) -proc `[]`*[A, B](t: TableRef[A, B], key: A): var B {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. If ``key`` is not in ``t``, the - ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether - ## the key exists. - result = t[][key] +proc contains*[A, B](t: TableRef[A, B], key: A): bool = + ## Alias of `hasKey proc<#hasKey,TableRef[A,B],A>`_ for use with + ## the ``in`` operator. + runnableExamples: + let a = {'a': 5, 'b': 9}.newTable + doAssert 'b' in a == true + doAssert a.contains('z') == false + return hasKey[A, B](t, key) -proc mget*[A, B](t: TableRef[A, B], key: A): var B {.deprecated.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. - t[][key] +proc hasKeyOrPut*[A, B](t: var TableRef[A, B], key: A, val: B): bool = + ## Returns true if ``key`` is in the table, otherwise inserts ``value``. + ## + ## See also: + ## * `hasKey proc<#hasKey,TableRef[A,B],A>`_ + ## * `[] proc<#[],TableRef[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.newTable + if a.hasKeyOrPut('a', 50): + a['a'] = 99 + if a.hasKeyOrPut('z', 50): + a['z'] = 99 + doAssert a == {'a': 99, 'b': 9, 'z': 50}.newTable + t[].hasKeyOrPut(key, val) proc getOrDefault*[A, B](t: TableRef[A, B], key: A): B = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, the + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. Otherwise, the ## default initialization value for type ``B`` is returned (e.g. 0 for any ## integer type). + ## + ## See also: + ## * `[] proc<#[],TableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,TableRef[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,TableRef[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,TableRef[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + let a = {'a': 5, 'b': 9}.newTable + doAssert a.getOrDefault('a') == 5 + doAssert a.getOrDefault('z') == 0 getOrDefault(t[], key) proc getOrDefault*[A, B](t: TableRef[A, B], key: A, default: B): B = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. ## Otherwise, ``default`` is returned. + ## + ## See also: + ## * `[] proc<#[],TableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,TableRef[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,TableRef[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,TableRef[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + runnableExamples: + let a = {'a': 5, 'b': 9}.newTable + doAssert a.getOrDefault('a', 99) == 5 + doAssert a.getOrDefault('z', 99) == 99 getOrDefault(t[], key, default) proc mgetOrPut*[A, B](t: TableRef[A, B], key: A, val: B): var B = - ## retrieves value at ``t[key]`` or puts ``val`` if not present, either way + ## Retrieves value at ``t[key]`` or puts ``val`` if not present, either way ## returning a value which can be modified. + ## + ## See also: + ## * `[] proc<#[],TableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,TableRef[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,TableRef[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.newTable + doAssert a.mgetOrPut('a', 99) == 5 + doAssert a.mgetOrPut('z', 99) == 99 + doAssert a == {'a': 5, 'b': 9, 'z': 99}.newTable t[].mgetOrPut(key, val) -proc hasKeyOrPut*[A, B](t: var TableRef[A, B], key: A, val: B): bool = - ## returns true iff ``key`` is in the table, otherwise inserts ``value``. - t[].hasKeyOrPut(key, val) - -proc contains*[A, B](t: TableRef[A, B], key: A): bool = - ## Alias of ``hasKey`` for use with the ``in`` operator. - return hasKey[A, B](t, key) - -proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) = - ## puts a ``(key, value)`` pair into ``t``. - t[][key] = val +proc len*[A, B](t: TableRef[A, B]): int = + ## Returns the number of keys in ``t``. + runnableExamples: + let a = {'a': 5, 'b': 9}.newTable + doAssert len(a) == 2 + result = t.counter proc add*[A, B](t: TableRef[A, B], key: A, val: B) = - ## puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. - ## This can introduce duplicate keys into the table! + ## Puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. + ## + ## **This can introduce duplicate keys into the table!** + ## + ## Use `[]= proc<#[]=,TableRef[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table without introducing duplicates. t[].add(key, val) proc del*[A, B](t: TableRef[A, B], key: A) = - ## deletes ``key`` from hash table ``t``. Does nothing if the key does not exist. + ## Deletes ``key`` from hash table ``t``. Does nothing if the key does not exist. + ## + ## See also: + ## * `take proc<#take,TableRef[A,B],A,B>`_ + ## * `clear proc<#clear,TableRef[A,B]>`_ to empty the whole table + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.newTable + a.del('a') + doAssert a == {'b': 9, 'c': 13}.newTable + a.del('z') + doAssert a == {'b': 9, 'c': 13}.newTable t[].del(key) proc take*[A, B](t: TableRef[A, B], key: A, val: var B): bool = - ## deletes the ``key`` from the table. + ## Deletes the ``key`` from the table. ## Returns ``true``, if the ``key`` existed, and sets ``val`` to the ## mapping of the key. Otherwise, returns ``false``, and the ``val`` is ## unchanged. + ## + ## See also: + ## * `del proc<#del,TableRef[A,B],A>`_ + ## * `clear proc<#clear,TableRef[A,B]>`_ to empty the whole table + runnableExamples: + var + a = {'a': 5, 'b': 9, 'c': 13}.newTable + i: int + doAssert a.take('b', i) == true + doAssert a == {'a': 5, 'c': 13}.newTable + doAssert i == 9 + i = 0 + doAssert a.take('z', i) == false + doAssert a == {'a': 5, 'c': 13}.newTable + doAssert i == 0 result = t[].take(key, val) -proc newTable*[A, B](initialSize=64): TableRef[A, B] = - new(result) - result[] = initTable[A, B](initialSize) - -proc newTable*[A, B](pairs: openArray[(A, B)]): TableRef[A, B] = - ## creates a new hash table that contains the given ``pairs``. - new(result) - result[] = toTable[A, B](pairs) +proc clear*[A, B](t: TableRef[A, B]) = + ## Resets the table so that it is empty. + ## + ## See also: + ## * `del proc<#del,Table[A,B],A>`_ + ## * `take proc<#take,Table[A,B],A,B>`_ + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.newTable + doAssert len(a) == 3 + clear(a) + doAssert len(a) == 0 + clearImpl() proc `$`*[A, B](t: TableRef[A, B]): string = - ## The ``$`` operator for hash tables. + ## The ``$`` operator for hash tables. Used internally when calling `echo` + ## on a table. dollarImpl() proc `==`*[A, B](s, t: TableRef[A, B]): bool = - ## The ``==`` operator for hash tables. Returns ``true`` iff either both tables - ## are ``nil`` or none is ``nil`` and the content of both tables contains the + ## The ``==`` operator for hash tables. Returns ``true`` if either both tables + ## are ``nil``, or neither is ``nil`` and the content of both tables contains the ## same key-value pairs. Insert order does not matter. + runnableExamples: + let + a = {'a': 5, 'b': 9, 'c': 13}.newTable + b = {'b': 9, 'c': 13, 'a': 5}.newTable + doAssert a == b if isNil(s): result = isNil(t) elif isNil(t): result = false else: equalsImpl(s[], t[]) -proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): TableRef[C, B] = - ## Index the collection with the proc provided. - # TODO: As soon as supported, change collection: A to collection: A[B] - result = newTable[C, B]() - for item in collection: - result[index(item)] = item -# ------------------------------ ordered table ------------------------------ -type - OrderedKeyValuePair[A, B] = tuple[ - hcode: Hash, next: int, key: A, val: B] - OrderedKeyValuePairSeq[A, B] = seq[OrderedKeyValuePair[A, B]] - OrderedTable* [A, B] = object ## table that remembers insertion order - data: OrderedKeyValuePairSeq[A, B] - counter, first, last: int - OrderedTableRef*[A, B] = ref OrderedTable[A, B] - -proc len*[A, B](t: OrderedTable[A, B]): int {.inline.} = - ## returns the number of keys in ``t``. - result = t.counter +iterator pairs*[A, B](t: TableRef[A, B]): (A, B) = + ## Iterates over any ``(key, value)`` pair in the table ``t``. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,TableRef[A,B]>`_ + ## * `keys iterator<#keys.i,TableRef[A,B]>`_ + ## * `values iterator<#values.i,TableRef[A,B]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## let a = { + ## 'o': [1, 5, 7, 9], + ## 'e': [2, 4, 6, 8] + ## }.newTable + ## + ## for k, v in a.pairs: + ## echo "key: ", k + ## echo "value: ", v + ## + ## # key: e + ## # value: [2, 4, 6, 8] + ## # key: o + ## # value: [1, 5, 7, 9] + for h in 0..high(t.data): + if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) -proc clear*[A, B](t: var OrderedTable[A, B]) = - ## resets the table so that it is empty. - clearImpl() - t.first = -1 - t.last = -1 +iterator mpairs*[A, B](t: TableRef[A, B]): (A, var B) = + ## Iterates over any ``(key, value)`` pair in the table ``t``. The values + ## can be modified. + ## + ## See also: + ## * `pairs iterator<#pairs.i,TableRef[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,TableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newTable + for k, v in a.mpairs: + v.add(v[0] + 10) + doAssert a == {'e': @[2, 4, 6, 8, 12], 'o': @[1, 5, 7, 9, 11]}.newTable + for h in 0..high(t.data): + if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) -proc clear*[A, B](t: var OrderedTableRef[A, B]) = - ## resets the table so that is is empty. - clear(t[]) +iterator keys*[A, B](t: TableRef[A, B]): A = + ## Iterates over any key in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,TableRef[A,B]>`_ + ## * `values iterator<#values.i,TableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newTable + for k in a.keys: + a[k].add(99) + doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.newTable + for h in 0..high(t.data): + if isFilled(t.data[h].hcode): yield t.data[h].key -template forAllOrderedPairs(yieldStmt: untyped): typed {.dirty.} = - var h = t.first - while h >= 0: - var nxt = t.data[h].next - if isFilled(t.data[h].hcode): yieldStmt - h = nxt +iterator values*[A, B](t: TableRef[A, B]): B = + ## Iterates over any value in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,TableRef[A,B]>`_ + ## * `keys iterator<#keys.i,TableRef[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,TableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newTable + for v in a.values: + doAssert v.len == 4 + for h in 0..high(t.data): + if isFilled(t.data[h].hcode): yield t.data[h].val -iterator pairs*[A, B](t: OrderedTable[A, B]): (A, B) = - ## iterates over any ``(key, value)`` pair in the table ``t`` in insertion - ## order. - forAllOrderedPairs: - yield (t.data[h].key, t.data[h].val) +iterator mvalues*[A, B](t: TableRef[A, B]): var B = + ## Iterates over any value in the table ``t``. The values can be modified. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,TableRef[A,B]>`_ + ## * `values iterator<#values.i,TableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newTable + for v in a.mvalues: + v.add(99) + doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.newTable + for h in 0..high(t.data): + if isFilled(t.data[h].hcode): yield t.data[h].val -iterator mpairs*[A, B](t: var OrderedTable[A, B]): (A, var B) = - ## iterates over any ``(key, value)`` pair in the table ``t`` in insertion - ## order. The values can be modified. - forAllOrderedPairs: - yield (t.data[h].key, t.data[h].val) -iterator keys*[A, B](t: OrderedTable[A, B]): A = - ## iterates over any key in the table ``t`` in insertion order. - forAllOrderedPairs: - yield t.data[h].key -iterator values*[A, B](t: OrderedTable[A, B]): B = - ## iterates over any value in the table ``t`` in insertion order. - forAllOrderedPairs: - yield t.data[h].val -iterator mvalues*[A, B](t: var OrderedTable[A, B]): var B = - ## iterates over any value in the table ``t`` in insertion order. The values - ## can be modified. - forAllOrderedPairs: - yield t.data[h].val -proc rawGetKnownHC[A, B](t: OrderedTable[A, B], key: A, hc: Hash): int = - rawGetKnownHCImpl() -proc rawGetDeep[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int {.inline.} = - rawGetDeepImpl() -proc rawGet[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int = - rawGetImpl() -proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. If ``key`` is not in ``t``, the - ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether - ## the key exists. - get(t, key) +# --------------------------------------------------------------------------- +# ------------------------------ OrderedTable ------------------------------- +# --------------------------------------------------------------------------- -proc `[]`*[A, B](t: var OrderedTable[A, B], key: A): var B{.deprecatedGet.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - get(t, key) +type + OrderedKeyValuePair[A, B] = tuple[ + hcode: Hash, next: int, key: A, val: B] + OrderedKeyValuePairSeq[A, B] = seq[OrderedKeyValuePair[A, B]] + OrderedTable* [A, B] = object + ## Hash table that remembers insertion order. + ## + ## For creating an empty OrderedTable, use `initOrderedTable proc + ## <#initOrderedTable,int>`_. + data: OrderedKeyValuePairSeq[A, B] + counter, first, last: int + OrderedTableRef*[A, B] = ref OrderedTable[A, B] ## Ref version of + ## `OrderedTable<#OrderedTable>`_. + ## + ## For creating a new empty OrderedTableRef, use `newOrderedTable proc + ## <#newOrderedTable,int>`_. -proc mget*[A, B](t: var OrderedTable[A, B], key: A): var B {.deprecated.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. - get(t, key) -proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A): B = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, the - ## default initialization value for type ``B`` is returned (e.g. 0 for any - ## integer type). - getOrDefaultImpl(t, key) +# ------------------------------ helpers --------------------------------- -proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A, default: B): B = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, - ## ``default`` is returned. - getOrDefaultImpl(t, key, default) +proc rawGetKnownHC[A, B](t: OrderedTable[A, B], key: A, hc: Hash): int = + rawGetKnownHCImpl() -proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool = - ## returns true iff ``key`` is in the table ``t``. - var hc: Hash - result = rawGet(t, key, hc) >= 0 +proc rawGetDeep[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int {.inline.} = + rawGetDeepImpl() -proc contains*[A, B](t: OrderedTable[A, B], key: A): bool = - ## Alias of ``hasKey`` for use with the ``in`` operator. - return hasKey[A, B](t, key) +proc rawGet[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int = + rawGetImpl() proc rawInsert[A, B](t: var OrderedTable[A, B], data: var OrderedKeyValuePairSeq[A, B], @@ -639,67 +1207,283 @@ proc enlarge[A, B](t: var OrderedTable[A, B]) = rawInsert(t, t.data, n[h].key, n[h].val, n[h].hcode, j) h = nxt +template forAllOrderedPairs(yieldStmt: untyped): typed {.dirty.} = + var h = t.first + while h >= 0: + var nxt = t.data[h].next + if isFilled(t.data[h].hcode): yieldStmt + h = nxt + +# ---------------------------------------------------------------------- + +proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] = + ## Creates a new ordered hash table that is empty. + ## + ## ``initialSize`` must be a power of two (default: 64). + ## If you need to accept runtime values for this you could use the + ## `nextPowerOfTwo proc`_ from the + ## `math module`_ or the `rightSize proc<#rightSize,Natural>`_ + ## from this module. + ## + ## See also: + ## * `toOrderedTable proc<#toOrderedTable,openArray[]>`_ + ## * `newOrderedTable proc<#newOrderedTable,int>`_ for creating an + ## `OrderedTableRef` + runnableExamples: + let + a = initOrderedTable[int, string]() + b = initOrderedTable[char, seq[int]]() + assert isPowerOfTwo(initialSize) + result.counter = 0 + result.first = -1 + result.last = -1 + newSeq(result.data, initialSize) + +proc toOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTable[A, B] = + ## Creates a new ordered hash table that contains the given ``pairs``. + ## + ## ``pairs`` is a container consisting of ``(key, value)`` tuples. + ## + ## See also: + ## * `initOrderedTable proc<#initOrderedTable,int>`_ + ## * `newOrderedTable proc<#newOrderedTable,openArray[]>`_ for an + ## `OrderedTableRef` version + runnableExamples: + let a = [('a', 5), ('b', 9)] + let b = toOrderedTable(a) + assert b == {'a': 5, 'b': 9}.toOrderedTable + result = initOrderedTable[A, B](rightSize(pairs.len)) + for key, val in items(pairs): result[key] = val + +proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B = + ## Retrieves the value at ``t[key]``. + ## + ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + ## One can check with `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ whether + ## the key exists. + ## + ## See also: + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + ## * `[]= proc<#[]=,OrderedTable[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ for checking if a + ## key is in the table + runnableExamples: + let a = {'a': 5, 'b': 9}.toOrderedTable + doAssert a['a'] == 5 + doAssertRaises(KeyError): + echo a['z'] + get(t, key) + +proc `[]`*[A, B](t: var OrderedTable[A, B], key: A): var B= + ## Retrieves the value at ``t[key]``. The value can be modified. + ## + ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + ## + ## See also: + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + ## * `[]= proc<#[]=,OrderedTable[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ for checking if a + ## key is in the table + get(t, key) + proc `[]=`*[A, B](t: var OrderedTable[A, B], key: A, val: B) = - ## puts a ``(key, value)`` pair into ``t``. + ## Inserts a ``(key, value)`` pair into ``t``. + ## + ## See also: + ## * `[] proc<#[],OrderedTable[A,B],A>`_ for retrieving a value of a key + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTable[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,OrderedTable[A,B],A,B>`_ + ## * `del proc<#del,OrderedTable[A,B],A>`_ for removing a key from the table + runnableExamples: + var a = initOrderedTable[char, int]() + a['x'] = 7 + a['y'] = 33 + doAssert a == {'x': 7, 'y': 33}.toOrderedTable putImpl(enlarge) -proc add*[A, B](t: var OrderedTable[A, B], key: A, val: B) = - ## puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. - ## This can introduce duplicate keys into the table! - addImpl(enlarge) +proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool = + ## Returns true if ``key`` is in the table ``t``. + ## + ## See also: + ## * `contains proc<#contains,OrderedTable[A,B],A>`_ for use with the `in` + ## operator + ## * `[] proc<#[],OrderedTable[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + let a = {'a': 5, 'b': 9}.toOrderedTable + doAssert a.hasKey('a') == true + doAssert a.hasKey('z') == false + var hc: Hash + result = rawGet(t, key, hc) >= 0 + +proc contains*[A, B](t: OrderedTable[A, B], key: A): bool = + ## Alias of `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ for use with + ## the ``in`` operator. + runnableExamples: + let a = {'a': 5, 'b': 9}.toOrderedTable + doAssert 'b' in a == true + doAssert a.contains('z') == false + return hasKey[A, B](t, key) + +proc hasKeyOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): bool = + ## Returns true if ``key`` is in the table, otherwise inserts ``value``. + ## + ## See also: + ## * `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ + ## * `[] proc<#[],OrderedTable[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.toOrderedTable + if a.hasKeyOrPut('a', 50): + a['a'] = 99 + if a.hasKeyOrPut('z', 50): + a['z'] = 99 + doAssert a == {'a': 99, 'b': 9, 'z': 50}.toOrderedTable + hasKeyOrPutImpl(enlarge) + +proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A): B = + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. Otherwise, the + ## default initialization value for type ``B`` is returned (e.g. 0 for any + ## integer type). + ## + ## See also: + ## * `[] proc<#[],OrderedTable[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTable[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,OrderedTable[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + let a = {'a': 5, 'b': 9}.toOrderedTable + doAssert a.getOrDefault('a') == 5 + doAssert a.getOrDefault('z') == 0 + getOrDefaultImpl(t, key) + +proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A, default: B): B = + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. + ## Otherwise, ``default`` is returned. + ## + ## See also: + ## * `[] proc<#[],OrderedTable[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTable[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,OrderedTable[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + runnableExamples: + let a = {'a': 5, 'b': 9}.toOrderedTable + doAssert a.getOrDefault('a', 99) == 5 + doAssert a.getOrDefault('z', 99) == 99 + getOrDefaultImpl(t, key, default) proc mgetOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): var B = - ## retrieves value at ``t[key]`` or puts ``value`` if not present, either way + ## Retrieves value at ``t[key]`` or puts ``val`` if not present, either way ## returning a value which can be modified. + ## + ## See also: + ## * `[] proc<#[],OrderedTable[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTable[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.toOrderedTable + doAssert a.mgetOrPut('a', 99) == 5 + doAssert a.mgetOrPut('z', 99) == 99 + doAssert a == {'a': 5, 'b': 9, 'z': 99}.toOrderedTable mgetOrPutImpl(enlarge) -proc hasKeyOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): bool = - ## returns true iff ``key`` is in the table, otherwise inserts ``value``. - hasKeyOrPutImpl(enlarge) +proc len*[A, B](t: OrderedTable[A, B]): int {.inline.} = + ## Returns the number of keys in ``t``. + runnableExamples: + let a = {'a': 5, 'b': 9}.toOrderedTable + doAssert len(a) == 2 + result = t.counter -proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] = - ## creates a new ordered hash table that is empty. +proc add*[A, B](t: var OrderedTable[A, B], key: A, val: B) = + ## Puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. ## - ## ``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 or the ``rightSize`` proc from this module. - assert isPowerOfTwo(initialSize) - result.counter = 0 - result.first = -1 - result.last = -1 - newSeq(result.data, initialSize) - -proc toOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTable[A, B] = - ## creates a new ordered hash table that contains the given ``pairs``. - result = initOrderedTable[A, B](rightSize(pairs.len)) - for key, val in items(pairs): result[key] = val + ## **This can introduce duplicate keys into the table!** + ## + ## Use `[]= proc<#[]=,OrderedTable[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table without introducing duplicates. + addImpl(enlarge) -proc `$`*[A, B](t: OrderedTable[A, B]): string = - ## The ``$`` operator for ordered hash tables. - dollarImpl() +proc del*[A, B](t: var OrderedTable[A, B], key: A) = + ## Deletes ``key`` from hash table ``t``. Does nothing if the key does not exist. + ## + ## O(n) complexity. + ## + ## See also: + ## * `clear proc<#clear,OrderedTable[A,B]>`_ to empty the whole table + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.toOrderedTable + a.del('a') + doAssert a == {'b': 9, 'c': 13}.toOrderedTable + a.del('z') + doAssert a == {'b': 9, 'c': 13}.toOrderedTable + 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) + 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: + 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 `==`*[A, B](s, t: OrderedTable[A, B]): bool = - ## The ``==`` operator for ordered hash tables. Returns true iff both the - ## content and the order are equal. - if s.counter != t.counter: - return false - var ht = t.first - var hs = s.first - while ht >= 0 and hs >= 0: - var nxtt = t.data[ht].next - var nxts = s.data[hs].next - if isFilled(t.data[ht].hcode) and isFilled(s.data[hs].hcode): - if (s.data[hs].key != t.data[ht].key) or (s.data[hs].val != t.data[ht].val): - return false - ht = nxtt - hs = nxts - return true +proc clear*[A, B](t: var OrderedTable[A, B]) = + ## Resets the table so that it is empty. + ## + ## See also: + ## * `del proc<#del,OrderedTable[A,B],A>`_ + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.toOrderedTable + doAssert len(a) == 3 + clear(a) + doAssert len(a) == 0 + clearImpl() + t.first = -1 + t.last = -1 proc sort*[A, B](t: var OrderedTable[A, B], cmp: proc (x,y: (A, B)): int) = - ## sorts ``t`` according to ``cmp``. This modifies the internal list + ## Sorts ``t`` according to the function ``cmp``. + ## + ## This modifies the internal list ## that kept the insertion order, so insertion order is lost after this ## call but key lookup and insertions remain possible after ``sort`` (in - ## contrast to the ``sort`` for count tables). + ## contrast to the `sort proc<#sort,CountTable[A]>`_ for count tables). + runnableExamples: + var a = initOrderedTable[char, int]() + for i, c in "cab": + a[c] = 10*i + doAssert a == {'c': 0, 'a': 10, 'b': 20}.toOrderedTable + a.sort(system.cmp) + doAssert a == {'a': 10, 'b': 20, 'c': 0}.toOrderedTable + var list = t.first var p, q, e, tail, oldhead: int @@ -740,198 +1524,519 @@ proc sort*[A, B](t: var OrderedTable[A, B], cmp: proc (x,y: (A, B)): int) = t.first = list t.last = tail -proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} = - ## returns the number of keys in ``t``. - result = t.counter +proc `$`*[A, B](t: OrderedTable[A, B]): string = + ## The ``$`` operator for ordered hash tables. Used internally when calling + ## `echo` on a table. + dollarImpl() -iterator pairs*[A, B](t: OrderedTableRef[A, B]): (A, B) = - ## iterates over any ``(key, value)`` pair in the table ``t`` in insertion +proc `==`*[A, B](s, t: OrderedTable[A, B]): bool = + ## The ``==`` operator for ordered hash tables. Returns ``true`` if both the + ## content and the order are equal. + runnableExamples: + let + a = {'a': 5, 'b': 9, 'c': 13}.toOrderedTable + b = {'b': 9, 'c': 13, 'a': 5}.toOrderedTable + doAssert a != b + + if s.counter != t.counter: + return false + var ht = t.first + var hs = s.first + while ht >= 0 and hs >= 0: + var nxtt = t.data[ht].next + var nxts = s.data[hs].next + if isFilled(t.data[ht].hcode) and isFilled(s.data[hs].hcode): + if (s.data[hs].key != t.data[ht].key) or (s.data[hs].val != t.data[ht].val): + return false + ht = nxtt + hs = nxts + return true + + + +iterator pairs*[A, B](t: OrderedTable[A, B]): (A, B) = + ## Iterates over any ``(key, value)`` pair in the table ``t`` in insertion ## order. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,OrderedTable[A,B]>`_ + ## * `keys iterator<#keys.i,OrderedTable[A,B]>`_ + ## * `values iterator<#values.i,OrderedTable[A,B]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## let a = { + ## 'o': [1, 5, 7, 9], + ## 'e': [2, 4, 6, 8] + ## }.toOrderedTable + ## + ## for k, v in a.pairs: + ## echo "key: ", k + ## echo "value: ", v + ## + ## # key: o + ## # value: [1, 5, 7, 9] + ## # key: e + ## # value: [2, 4, 6, 8] forAllOrderedPairs: yield (t.data[h].key, t.data[h].val) -iterator mpairs*[A, B](t: OrderedTableRef[A, B]): (A, var B) = - ## iterates over any ``(key, value)`` pair in the table ``t`` in insertion - ## order. The values can be modified. +iterator mpairs*[A, B](t: var OrderedTable[A, B]): (A, var B) = + ## Iterates over any ``(key, value)`` pair in the table ``t`` (must be + ## declared as `var`) in insertion order. The values can be modified. + ## + ## See also: + ## * `pairs iterator<#pairs.i,OrderedTable[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,OrderedTable[A,B]>`_ + runnableExamples: + var a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toOrderedTable + for k, v in a.mpairs: + v.add(v[0] + 10) + doAssert a == {'o': @[1, 5, 7, 9, 11], 'e': @[2, 4, 6, 8, 12]}.toOrderedTable forAllOrderedPairs: yield (t.data[h].key, t.data[h].val) -iterator keys*[A, B](t: OrderedTableRef[A, B]): A = - ## iterates over any key in the table ``t`` in insertion order. +iterator keys*[A, B](t: OrderedTable[A, B]): A = + ## Iterates over any key in the table ``t`` in insertion order. + ## + ## See also: + ## * `pairs iterator<#pairs.i,OrderedTable[A,B]>`_ + ## * `values iterator<#values.i,OrderedTable[A,B]>`_ + runnableExamples: + var a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toOrderedTable + for k in a.keys: + a[k].add(99) + doAssert a == {'o': @[1, 5, 7, 9, 99], 'e': @[2, 4, 6, 8, 99]}.toOrderedTable forAllOrderedPairs: yield t.data[h].key -iterator values*[A, B](t: OrderedTableRef[A, B]): B = - ## iterates over any value in the table ``t`` in insertion order. +iterator values*[A, B](t: OrderedTable[A, B]): B = + ## Iterates over any value in the table ``t`` in insertion order. + ## + ## See also: + ## * `pairs iterator<#pairs.i,OrderedTable[A,B]>`_ + ## * `keys iterator<#keys.i,OrderedTable[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,OrderedTable[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toOrderedTable + for v in a.values: + doAssert v.len == 4 forAllOrderedPairs: yield t.data[h].val -iterator mvalues*[A, B](t: OrderedTableRef[A, B]): var B = - ## iterates over any value in the table ``t`` in insertion order. The values +iterator mvalues*[A, B](t: var OrderedTable[A, B]): var B = + ## Iterates over any value in the table ``t`` (must be + ## declared as `var`) in insertion order. The values ## can be modified. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,OrderedTable[A,B]>`_ + ## * `values iterator<#values.i,OrderedTable[A,B]>`_ + runnableExamples: + var a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toOrderedTable + for v in a.mvalues: + v.add(99) + doAssert a == {'o': @[1, 5, 7, 9, 99], 'e': @[2, 4, 6, 8, 99]}.toOrderedTable forAllOrderedPairs: yield t.data[h].val + + + + +# --------------------------------------------------------------------------- +# --------------------------- OrderedTableRef ------------------------------- +# --------------------------------------------------------------------------- + +proc newOrderedTable*[A, B](initialSize=64): OrderedTableRef[A, B] = + ## Creates a new ordered ref hash table that is empty. + ## + ## ``initialSize`` must be a power of two (default: 64). + ## If you need to accept runtime values for this you could use the + ## `nextPowerOfTwo proc`_ from the + ## `math module`_ or the `rightSize proc<#rightSize,Natural>`_ + ## from this module. + ## + ## See also: + ## * `newOrderedTable proc<#newOrderedTable,openArray[]>`_ for creating + ## an `OrderedTableRef` from a collection of `(key, value)` pairs + ## * `initOrderedTable proc<#initOrderedTable,int>`_ for creating an + ## `OrderedTable` + runnableExamples: + let + a = newOrderedTable[int, string]() + b = newOrderedTable[char, seq[int]]() + new(result) + result[] = initOrderedTable[A, B](initialSize) + +proc newOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTableRef[A, B] = + ## Creates a new ordered ref hash table that contains the given ``pairs``. + ## + ## ``pairs`` is a container consisting of ``(key, value)`` tuples. + ## + ## See also: + ## * `newOrderedTable proc<#newOrderedTable,int>`_ + ## * `toOrderedTable proc<#toOrderedTable,openArray[]>`_ for an + ## `OrderedTable` version + runnableExamples: + let a = [('a', 5), ('b', 9)] + let b = newOrderedTable(a) + assert b == {'a': 5, 'b': 9}.newOrderedTable + result = newOrderedTable[A, B](rightSize(pairs.len)) + for key, val in items(pairs): result.add(key, val) + + proc `[]`*[A, B](t: OrderedTableRef[A, B], key: A): var B = - ## retrieves the value at ``t[key]``. If ``key`` is not in ``t``, the - ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether + ## Retrieves the value at ``t[key]``. + ## + ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + ## One can check with `hasKey proc<#hasKey,OrderedTableRef[A,B],A>`_ whether ## the key exists. + ## + ## See also: + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + ## * `[]= proc<#[]=,OrderedTableRef[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,OrderedTableRef[A,B],A>`_ for checking if + ## a key is in the table + runnableExamples: + let a = {'a': 5, 'b': 9}.newOrderedTable + doAssert a['a'] == 5 + doAssertRaises(KeyError): + echo a['z'] result = t[][key] -proc mget*[A, B](t: OrderedTableRef[A, B], key: A): var B {.deprecated.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. - result = t[][key] +proc `[]=`*[A, B](t: OrderedTableRef[A, B], key: A, val: B) = + ## Inserts a ``(key, value)`` pair into ``t``. + ## + ## See also: + ## * `[] proc<#[],OrderedTableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTableRef[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,OrderedTableRef[A,B],A,B>`_ + ## * `del proc<#del,OrderedTableRef[A,B],A>`_ for removing a key from the table + runnableExamples: + var a = newOrderedTable[char, int]() + a['x'] = 7 + a['y'] = 33 + doAssert a == {'x': 7, 'y': 33}.newOrderedTable + t[][key] = val + +proc hasKey*[A, B](t: OrderedTableRef[A, B], key: A): bool = + ## Returns true if ``key`` is in the table ``t``. + ## + ## See also: + ## * `contains proc<#contains,OrderedTableRef[A,B],A>`_ for use with the `in` + ## operator + ## * `[] proc<#[],OrderedTableRef[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + let a = {'a': 5, 'b': 9}.newOrderedTable + doAssert a.hasKey('a') == true + doAssert a.hasKey('z') == false + result = t[].hasKey(key) + +proc contains*[A, B](t: OrderedTableRef[A, B], key: A): bool = + ## Alias of `hasKey proc<#hasKey,OrderedTableRef[A,B],A>`_ for use with + ## the ``in`` operator. + runnableExamples: + let a = {'a': 5, 'b': 9}.newOrderedTable + doAssert 'b' in a == true + doAssert a.contains('z') == false + return hasKey[A, B](t, key) + +proc hasKeyOrPut*[A, B](t: var OrderedTableRef[A, B], key: A, val: B): bool = + ## Returns true if ``key`` is in the table, otherwise inserts ``value``. + ## + ## See also: + ## * `hasKey proc<#hasKey,OrderedTableRef[A,B],A>`_ + ## * `[] proc<#[],OrderedTableRef[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.newOrderedTable + if a.hasKeyOrPut('a', 50): + a['a'] = 99 + if a.hasKeyOrPut('z', 50): + a['z'] = 99 + doAssert a == {'a': 99, 'b': 9, 'z': 50}.newOrderedTable + result = t[].hasKeyOrPut(key, val) proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A): B = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, the + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. Otherwise, the ## default initialization value for type ``B`` is returned (e.g. 0 for any ## integer type). + ## + ## See also: + ## * `[] proc<#[],OrderedTableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,OrderedTableRef[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTableRef[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,OrderedTableRef[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + let a = {'a': 5, 'b': 9}.newOrderedTable + doAssert a.getOrDefault('a') == 5 + doAssert a.getOrDefault('z') == 0 getOrDefault(t[], key) proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A, default: B): B = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, - ## ``default`` is returned. + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. + ## Otherwise, ``default`` is returned. + ## + ## See also: + ## * `[] proc<#[],OrderedTableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,OrderedTableRef[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTableRef[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,OrderedTableRef[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + runnableExamples: + let a = {'a': 5, 'b': 9}.newOrderedTable + doAssert a.getOrDefault('a', 99) == 5 + doAssert a.getOrDefault('z', 99) == 99 getOrDefault(t[], key, default) proc mgetOrPut*[A, B](t: OrderedTableRef[A, B], key: A, val: B): var B = - ## retrieves value at ``t[key]`` or puts ``val`` if not present, either way + ## Retrieves value at ``t[key]`` or puts ``val`` if not present, either way ## returning a value which can be modified. + ## + ## See also: + ## * `[] proc<#[],OrderedTableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,OrderedTableRef[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTableRef[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.newOrderedTable + doAssert a.mgetOrPut('a', 99) == 5 + doAssert a.mgetOrPut('z', 99) == 99 + doAssert a == {'a': 5, 'b': 9, 'z': 99}.newOrderedTable result = t[].mgetOrPut(key, val) -proc hasKeyOrPut*[A, B](t: var OrderedTableRef[A, B], key: A, val: B): bool = - ## returns true iff ``key`` is in the table, otherwise inserts ``val``. - result = t[].hasKeyOrPut(key, val) - -proc hasKey*[A, B](t: OrderedTableRef[A, B], key: A): bool = - ## returns true iff ``key`` is in the table ``t``. - result = t[].hasKey(key) - -proc contains*[A, B](t: OrderedTableRef[A, B], key: A): bool = - ## Alias of ``hasKey`` for use with the ``in`` operator. - return hasKey[A, B](t, key) - -proc `[]=`*[A, B](t: OrderedTableRef[A, B], key: A, val: B) = - ## puts a ``(key, value)`` pair into ``t``. - t[][key] = val +proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} = + ## Returns the number of keys in ``t``. + runnableExamples: + let a = {'a': 5, 'b': 9}.newOrderedTable + doAssert len(a) == 2 + result = t.counter proc add*[A, B](t: OrderedTableRef[A, B], key: A, val: B) = - ## puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. - ## This can introduce duplicate keys into the table! + ## Puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. + ## + ## **This can introduce duplicate keys into the table!** + ## + ## Use `[]= proc<#[]=,OrderedTableRef[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table without introducing duplicates. t[].add(key, val) -proc newOrderedTable*[A, B](initialSize=64): OrderedTableRef[A, B] = - ## creates a new ordered hash table that is empty. +proc del*[A, B](t: var OrderedTableRef[A, B], key: A) = + ## Deletes ``key`` from hash table ``t``. Does nothing if the key does not exist. ## - ## ``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 or the ``rightSize`` proc from this module. - new(result) - result[] = initOrderedTable[A, B](initialSize) + ## See also: + ## * `clear proc<#clear,OrderedTableRef[A,B]>`_ to empty the whole table + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.newOrderedTable + a.del('a') + doAssert a == {'b': 9, 'c': 13}.newOrderedTable + a.del('z') + doAssert a == {'b': 9, 'c': 13}.newOrderedTable + t[].del(key) -proc newOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTableRef[A, B] = - ## creates a new ordered hash table that contains the given ``pairs``. - result = newOrderedTable[A, B](rightSize(pairs.len)) - for key, val in items(pairs): result.add(key, val) +proc clear*[A, B](t: var OrderedTableRef[A, B]) = + ## Resets the table so that it is empty. + ## + ## See also: + ## * `del proc<#del,OrderedTable[A,B],A>`_ + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.newOrderedTable + doAssert len(a) == 3 + clear(a) + doAssert len(a) == 0 + clear(t[]) + +proc sort*[A, B](t: OrderedTableRef[A, B], cmp: proc (x,y: (A, B)): int) = + ## Sorts ``t`` according to the function ``cmp``. + ## + ## This modifies the internal list + ## that kept the insertion order, so insertion order is lost after this + ## call but key lookup and insertions remain possible after ``sort`` (in + ## contrast to the `sort proc<#sort,CountTableRef[A]>`_ for count tables). + runnableExamples: + var a = newOrderedTable[char, int]() + for i, c in "cab": + a[c] = 10*i + doAssert a == {'c': 0, 'a': 10, 'b': 20}.newOrderedTable + a.sort(system.cmp) + doAssert a == {'a': 10, 'b': 20, 'c': 0}.newOrderedTable + t[].sort(cmp) proc `$`*[A, B](t: OrderedTableRef[A, B]): string = - ## The ``$`` operator for ordered hash tables. + ## The ``$`` operator for hash tables. Used internally when calling `echo` + ## on a table. dollarImpl() proc `==`*[A, B](s, t: OrderedTableRef[A, B]): bool = - ## The ``==`` operator for ordered hash tables. Returns true iff either both - ## tables are ``nil`` or none is ``nil`` and the content and the order of + ## The ``==`` operator for ordered hash tables. Returns true if either both + ## tables are ``nil``, or neither is ``nil`` and the content and the order of ## both are equal. + runnableExamples: + let + a = {'a': 5, 'b': 9, 'c': 13}.newOrderedTable + b = {'b': 9, 'c': 13, 'a': 5}.newOrderedTable + doAssert a != b if isNil(s): result = isNil(t) elif isNil(t): result = false else: result = s[] == t[] -proc sort*[A, B](t: OrderedTableRef[A, B], cmp: proc (x,y: (A, B)): int) = - ## sorts ``t`` according to ``cmp``. This modifies the internal list - ## that kept the insertion order, so insertion order is lost after this - ## call but key lookup and insertions remain possible after ``sort`` (in - ## contrast to the ``sort`` for count tables). - t[].sort(cmp) -proc del*[A, B](t: var OrderedTable[A, B], key: A) = - ## deletes ``key`` from ordered hash table ``t``. O(n) complexity. Does nothing - ## if the key does not exist. - 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) - 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: - 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) complexity. Does nothing - ## if the key does not exist. - t[].del(key) +iterator pairs*[A, B](t: OrderedTableRef[A, B]): (A, B) = + ## Iterates over any ``(key, value)`` pair in the table ``t`` in insertion + ## order. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,OrderedTableRef[A,B]>`_ + ## * `keys iterator<#keys.i,OrderedTableRef[A,B]>`_ + ## * `values iterator<#values.i,OrderedTableRef[A,B]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## let a = { + ## 'o': [1, 5, 7, 9], + ## 'e': [2, 4, 6, 8] + ## }.newOrderedTable + ## + ## for k, v in a.pairs: + ## echo "key: ", k + ## echo "value: ", v + ## + ## # key: o + ## # value: [1, 5, 7, 9] + ## # key: e + ## # value: [2, 4, 6, 8] + forAllOrderedPairs: + yield (t.data[h].key, t.data[h].val) -# ------------------------------ count tables ------------------------------- +iterator mpairs*[A, B](t: OrderedTableRef[A, B]): (A, var B) = + ## Iterates over any ``(key, value)`` pair in the table ``t`` in insertion + ## order. The values can be modified. + ## + ## See also: + ## * `pairs iterator<#pairs.i,OrderedTableRef[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,OrderedTableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newOrderedTable + for k, v in a.mpairs: + v.add(v[0] + 10) + doAssert a == {'o': @[1, 5, 7, 9, 11], 'e': @[2, 4, 6, 8, 12]}.newOrderedTable + forAllOrderedPairs: + yield (t.data[h].key, t.data[h].val) -type - CountTable* [ - A] = object ## table that counts the number of each key - data: seq[tuple[key: A, val: int]] - counter: int - CountTableRef*[A] = ref CountTable[A] +iterator keys*[A, B](t: OrderedTableRef[A, B]): A = + ## Iterates over any key in the table ``t`` in insertion order. + ## + ## See also: + ## * `pairs iterator<#pairs.i,OrderedTableRef[A,B]>`_ + ## * `values iterator<#values.i,OrderedTableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newOrderedTable + for k in a.keys: + a[k].add(99) + doAssert a == {'o': @[1, 5, 7, 9, 99], 'e': @[2, 4, 6, 8, 99]}.newOrderedTable + forAllOrderedPairs: + yield t.data[h].key -proc len*[A](t: CountTable[A]): int = - ## returns the number of keys in ``t``. - result = t.counter +iterator values*[A, B](t: OrderedTableRef[A, B]): B = + ## Iterates over any value in the table ``t`` in insertion order. + ## + ## See also: + ## * `pairs iterator<#pairs.i,OrderedTableRef[A,B]>`_ + ## * `keys iterator<#keys.i,OrderedTableRef[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,OrderedTableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newOrderedTable + for v in a.values: + doAssert v.len == 4 + forAllOrderedPairs: + yield t.data[h].val -proc clear*[A](t: CountTableRef[A]) = - ## resets the table so that it is empty. - clearImpl() +iterator mvalues*[A, B](t: OrderedTableRef[A, B]): var B = + ## Iterates over any value in the table ``t`` in insertion order. The values + ## can be modified. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,OrderedTableRef[A,B]>`_ + ## * `values iterator<#values.i,OrderedTableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newOrderedTable + for v in a.mvalues: + v.add(99) + doAssert a == {'o': @[1, 5, 7, 9, 99], 'e': @[2, 4, 6, 8, 99]}.newOrderedTable + forAllOrderedPairs: + yield t.data[h].val -proc clear*[A](t: var CountTable[A]) = - ## resets the table so that it is empty. - clearImpl() -iterator pairs*[A](t: CountTable[A]): (A, int) = - ## iterates over any ``(key, value)`` pair in the table ``t``. - for h in 0..high(t.data): - if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) -iterator mpairs*[A](t: var CountTable[A]): (A, var int) = - ## iterates over any ``(key, value)`` pair in the table ``t``. The values can - ## be modified. - for h in 0..high(t.data): - if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) -iterator keys*[A](t: CountTable[A]): A = - ## iterates over any key in the table ``t``. - for h in 0..high(t.data): - if t.data[h].val != 0: yield t.data[h].key -iterator values*[A](t: CountTable[A]): int = - ## iterates over any value in the table ``t``. - for h in 0..high(t.data): - if t.data[h].val != 0: yield t.data[h].val -iterator mvalues*[A](t: CountTable[A]): var int = - ## iterates over any value in the table ``t``. The values can be modified. - for h in 0..high(t.data): - if t.data[h].val != 0: yield t.data[h].val -proc rawGet[A](t: CountTable[A], key: A): int = - var h: Hash = hash(key) and high(t.data) # start with real hash value - while t.data[h].val != 0: - if t.data[h].key == key: return h - h = nextTry(h, high(t.data)) - result = -1 - h # < 0 => MISSING; insert idx = -1 - result +# ------------------------------------------------------------------------- +# ------------------------------ CountTable ------------------------------- +# ------------------------------------------------------------------------- + +type + CountTable* [A] = object + ## Hash table that counts the number of each key. + ## + ## For creating an empty CountTable, use `initCountTable proc + ## <#initCountTable,int>`_. + data: seq[tuple[key: A, val: int]] + counter: int + CountTableRef*[A] = ref CountTable[A] ## Ref version of + ## `CountTable<#CountTable>`_. + ## + ## For creating a new empty CountTableRef, use `newCountTable proc + ## <#newCountTable,int>`_. + + +# ------------------------------ helpers --------------------------------- proc rawInsert[A](t: CountTable[A], data: var seq[tuple[key: A, val: int]], key: A, val: int) = @@ -947,46 +2052,87 @@ proc enlarge[A](t: var CountTable[A]) = if t.data[i].val != 0: rawInsert(t, n, t.data[i].key, t.data[i].val) swap(t.data, n) -proc `[]=`*[A](t: var CountTable[A], key: A, val: int) = - ## puts a ``(key, value)`` pair into ``t``. - assert val >= 0 - let h = rawGet(t, key) - if h >= 0: - t.data[h].val = val - else: - if mustRehash(len(t.data), t.counter): enlarge(t) - rawInsert(t, t.data, key, val) - inc(t.counter) +proc rawGet[A](t: CountTable[A], key: A): int = + var h: Hash = hash(key) and high(t.data) # start with real hash value + while t.data[h].val != 0: + if t.data[h].key == key: return h + h = nextTry(h, high(t.data)) + result = -1 - h # < 0 => MISSING; insert idx = -1 - result template ctget(t, key, default: untyped): untyped = var index = rawGet(t, key) result = if index >= 0: t.data[index].val else: default +proc inc*[A](t: var CountTable[A], key: A, val = 1) + +# ---------------------------------------------------------------------- + +proc initCountTable*[A](initialSize=64): CountTable[A] = + ## Creates a new count table that is empty. + ## + ## ``initialSize`` must be a power of two (default: 64). + ## If you need to accept runtime values for this you could use the + ## `nextPowerOfTwo proc`_ from the + ## `math module`_ or the `rightSize proc<#rightSize,Natural>`_ + ## from this module. + ## + ## See also: + ## * `toCountTable proc<#toCountTable,openArray[A]>`_ + ## * `newCountTable proc<#newCountTable,int>`_ for creating a + ## `CountTableRef` + assert isPowerOfTwo(initialSize) + result.counter = 0 + newSeq(result.data, initialSize) + +proc toCountTable*[A](keys: openArray[A]): CountTable[A] = + ## Creates a new count table with every member of a container ``keys`` + ## having a count of how many times it occurs in that container. + result = initCountTable[A](rightSize(keys.len)) + for key in items(keys): result.inc(key) + proc `[]`*[A](t: CountTable[A], key: A): int = ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. ## Otherwise ``0`` is returned. + ## + ## See also: + ## * `getOrDefault<#getOrDefault,CountTable[A],A,int>`_ to return + ## a custom value if the key doesn't exist + ## * `mget proc<#mget,CountTable[A],A>`_ + ## * `[]= proc<#[]%3D,CountTable[A],A,int>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,CountTable[A],A>`_ for checking if a key + ## is in the table ctget(t, key, 0) proc mget*[A](t: var CountTable[A], key: A): var int = ## Retrieves the value at ``t[key]``. The value can be modified. + ## ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. get(t, key) -proc getOrDefault*[A](t: CountTable[A], key: A; default: int = 0): int = - ## Retrieves the value at ``t[key]`` if``key`` is in ``t``. Otherwise, the - ## integer value of ``default`` is returned. - ctget(t, key, default) - -proc hasKey*[A](t: CountTable[A], key: A): bool = - ## returns true iff ``key`` is in the table ``t``. - result = rawGet(t, key) >= 0 - -proc contains*[A](t: CountTable[A], key: A): bool = - ## Alias of ``hasKey`` for use with the ``in`` operator. - return hasKey[A](t, key) +proc `[]=`*[A](t: var CountTable[A], key: A, val: int) = + ## Inserts a ``(key, value)`` pair into ``t``. + ## + ## See also: + ## * `[] proc<#[],CountTable[A],A>`_ for retrieving a value of a key + ## * `inc proc<#inc,CountTable[A],A,int>`_ for incrementing a + ## value of a key + assert val >= 0 + let h = rawGet(t, key) + if h >= 0: + t.data[h].val = val + else: + if mustRehash(len(t.data), t.counter): enlarge(t) + rawInsert(t, t.data, key, val) + inc(t.counter) proc inc*[A](t: var CountTable[A], key: A, val = 1) = - ## increments ``t[key]`` by ``val``. + ## Increments ``t[key]`` by ``val`` (default: 1). + runnableExamples: + var a = toCountTable("aab") + a.inc('a') + a.inc('b', 10) + doAssert a == toCountTable("aaabbbbbbbbbbb") var index = rawGet(t, key) if index >= 0: inc(t.data[index].val, val) @@ -996,33 +2142,11 @@ proc inc*[A](t: var CountTable[A], key: A, val = 1) = rawInsert(t, t.data, key, val) inc(t.counter) -proc initCountTable*[A](initialSize=64): CountTable[A] = - ## creates a new count table that is empty. - ## - ## ``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 or the ``rightSize`` proc in this module. - assert isPowerOfTwo(initialSize) - result.counter = 0 - newSeq(result.data, initialSize) - -proc toCountTable*[A](keys: openArray[A]): CountTable[A] = - ## creates a new count table with every key in ``keys`` having a count - ## of how many times it occurs in ``keys``. - result = initCountTable[A](rightSize(keys.len)) - for key in items(keys): result.inc(key) - -proc `$`*[A](t: CountTable[A]): string = - ## The ``$`` operator for count tables. - dollarImpl() - -proc `==`*[A](s, t: CountTable[A]): bool = - ## The ``==`` operator for count tables. Returns ``true`` iff both tables - ## contain the same keys with the same count. Insert order does not matter. - equalsImpl(s, t) - proc smallest*[A](t: CountTable[A]): tuple[key: A, val: int] = - ## returns the ``(key, value)`` pair with the smallest ``val``. Efficiency: O(n) + ## Returns the ``(key, value)`` pair with the smallest ``val``. Efficiency: O(n) + ## + ## See also: + ## * `largest proc<#largest,CountTable[A]>`_ assert t.len > 0 var minIdx = -1 for h in 0..high(t.data): @@ -1032,7 +2156,10 @@ proc smallest*[A](t: CountTable[A]): tuple[key: A, val: int] = result.val = t.data[minIdx].val proc largest*[A](t: CountTable[A]): tuple[key: A, val: int] = - ## returns the ``(key, value)`` pair with the largest ``val``. Efficiency: O(n) + ## Returns the ``(key, value)`` pair with the largest ``val``. Efficiency: O(n) + ## + ## See also: + ## * `smallest proc<#smallest,CountTable[A]>`_ assert t.len > 0 var maxIdx = 0 for h in 1..high(t.data): @@ -1040,11 +2167,49 @@ proc largest*[A](t: CountTable[A]): tuple[key: A, val: int] = result.key = t.data[maxIdx].key result.val = t.data[maxIdx].val +proc hasKey*[A](t: CountTable[A], key: A): bool = + ## Returns true if ``key`` is in the table ``t``. + ## + ## See also: + ## * `contains proc<#contains,CountTable[A],A>`_ for use with the `in` + ## operator + ## * `[] proc<#[],CountTable[A],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,CountTable[A],A,int>`_ to return + ## a custom value if the key doesn't exist + result = rawGet(t, key) >= 0 + +proc contains*[A](t: CountTable[A], key: A): bool = + ## Alias of `hasKey proc<#hasKey,CountTable[A],A>`_ for use with + ## the ``in`` operator. + return hasKey[A](t, key) + +proc getOrDefault*[A](t: CountTable[A], key: A; default: int = 0): int = + ## Retrieves the value at ``t[key]`` if``key`` is in ``t``. Otherwise, the + ## integer value of ``default`` is returned. + ## + ## See also: + ## * `[] proc<#[],CountTable[A],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,CountTable[A],A>`_ for checking if a key + ## is in the table + ctget(t, key, default) + +proc len*[A](t: CountTable[A]): int = + ## Returns the number of keys in ``t``. + result = t.counter + +proc clear*[A](t: var CountTable[A]) = + ## Resets the table so that it is empty. + clearImpl() + proc sort*[A](t: var CountTable[A]) = - ## sorts the count table so that the entry with the highest counter comes - ## first. This is destructive! You must not modify ``t`` afterwards! - ## You can use the iterators ``pairs``, ``keys``, and ``values`` to iterate over - ## ``t`` in the sorted order. + ## Sorts the count table so that the entry with the highest counter comes + ## first. + ## + ## **This is destructive! You must not modify ``t`` afterwards!** + ## + ## You can use the iterators `pairs<#pairs.i,CountTable[A]>`_, + ## `keys<#keys.i,CountTable[A]>`_, and `values<#values.i,CountTable[A]>`_ + ## to iterate over ``t`` in the sorted order. # we use shellsort here; fast enough and simple var h = 1 @@ -1061,130 +2226,373 @@ proc sort*[A](t: var CountTable[A]) = if j < h: break if h == 1: break -proc len*[A](t: CountTableRef[A]): int = - ## returns the number of keys in ``t``. - result = t.counter +proc merge*[A](s: var CountTable[A], t: CountTable[A]) = + ## Merges the second table into the first one (must be declared as `var`). + runnableExamples: + var a = toCountTable("aaabbc") + let b = toCountTable("bcc") + a.merge(b) + doAssert a == toCountTable("aaabbbccc") + for key, value in t: + s.inc(key, value) -iterator pairs*[A](t: CountTableRef[A]): (A, int) = - ## iterates over any ``(key, value)`` pair in the table ``t``. +proc merge*[A](s, t: CountTable[A]): CountTable[A] = + ## Merges the two tables into a new one. + runnableExamples: + let + a = toCountTable("aaabbc") + b = toCountTable("bcc") + doAssert merge(a, b) == toCountTable("aaabbbccc") + result = initCountTable[A](nextPowerOfTwo(max(s.len, t.len))) + for table in @[s, t]: + for key, value in table: + result.inc(key, value) + +proc `$`*[A](t: CountTable[A]): string = + ## The ``$`` operator for count tables. Used internally when calling `echo` + ## on a table. + dollarImpl() + +proc `==`*[A](s, t: CountTable[A]): bool = + ## The ``==`` operator for count tables. Returns ``true`` if both tables + ## contain the same keys with the same count. Insert order does not matter. + equalsImpl(s, t) + + +iterator pairs*[A](t: CountTable[A]): (A, int) = + ## Iterates over any ``(key, value)`` pair in the table ``t``. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,CountTable[A]>`_ + ## * `keys iterator<#keys.i,CountTable[A]>`_ + ## * `values iterator<#values.i,CountTable[A]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## let a = toCountTable("abracadabra") + ## + ## for k, v in pairs(a): + ## echo "key: ", k + ## echo "value: ", v + ## + ## # key: a + ## # value: 5 + ## # key: b + ## # value: 2 + ## # key: c + ## # value: 1 + ## # key: d + ## # value: 1 + ## # key: r + ## # value: 2 for h in 0..high(t.data): if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) -iterator mpairs*[A](t: CountTableRef[A]): (A, var int) = - ## iterates over any ``(key, value)`` pair in the table ``t``. The values can - ## be modified. +iterator mpairs*[A](t: var CountTable[A]): (A, var int) = + ## Iterates over any ``(key, value)`` pair in the table ``t`` (must be + ## declared as `var`). The values can be modified. + ## + ## See also: + ## * `pairs iterator<#pairs.i,CountTable[A]>`_ + ## * `mvalues iterator<#mvalues.i,CountTable[A]>`_ + runnableExamples: + var a = toCountTable("abracadabra") + for k, v in mpairs(a): + v = 2 + doAssert a == toCountTable("aabbccddrr") for h in 0..high(t.data): if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) -iterator keys*[A](t: CountTableRef[A]): A = - ## iterates over any key in the table ``t``. +iterator keys*[A](t: CountTable[A]): A = + ## Iterates over any key in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,CountTable[A]>`_ + ## * `values iterator<#values.i,CountTable[A]>`_ + runnableExamples: + var a = toCountTable("abracadabra") + for k in keys(a): + a[k] = 2 + doAssert a == toCountTable("aabbccddrr") for h in 0..high(t.data): if t.data[h].val != 0: yield t.data[h].key -iterator values*[A](t: CountTableRef[A]): int = - ## iterates over any value in the table ``t``. +iterator values*[A](t: CountTable[A]): int = + ## Iterates over any value in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,CountTable[A]>`_ + ## * `keys iterator<#keys.i,CountTable[A]>`_ + ## * `mvalues iterator<#mvalues.i,CountTable[A]>`_ + runnableExamples: + let a = toCountTable("abracadabra") + for v in values(a): + assert v < 10 for h in 0..high(t.data): if t.data[h].val != 0: yield t.data[h].val -iterator mvalues*[A](t: CountTableRef[A]): var int = - ## iterates over any value in the table ``t``. The values can be modified. +iterator mvalues*[A](t: var CountTable[A]): var int = + ## Iterates over any value in the table ``t`` (must be + ## declared as `var`). The values can be modified. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,CountTable[A]>`_ + ## * `values iterator<#values.i,CountTable[A]>`_ + runnableExamples: + var a = toCountTable("abracadabra") + for v in mvalues(a): + v = 2 + doAssert a == toCountTable("aabbccddrr") for h in 0..high(t.data): if t.data[h].val != 0: yield t.data[h].val + + + + + + +# --------------------------------------------------------------------------- +# ---------------------------- CountTableRef -------------------------------- +# --------------------------------------------------------------------------- + +proc inc*[A](t: CountTableRef[A], key: A, val = 1) + +proc newCountTable*[A](initialSize=64): CountTableRef[A] = + ## Creates a new ref count table that is empty. + ## + ## ``initialSize`` must be a power of two (default: 64). + ## If you need to accept runtime values for this you could use the + ## `nextPowerOfTwo proc`_ from the + ## `math module`_ or the `rightSize proc<#rightSize,Natural>`_ + ## from this module. + ## + ## See also: + ## * `newCountTable proc<#newCountTable,openArray[A]>`_ for creating + ## a `CountTableRef` from a collection + ## * `initCountTable proc<#initCountTable,int>`_ for creating a + ## `CountTable` + new(result) + result[] = initCountTable[A](initialSize) + +proc newCountTable*[A](keys: openArray[A]): CountTableRef[A] = + ## Creates a new ref count table with every member of a container ``keys`` + ## having a count of how many times it occurs in that container. + result = newCountTable[A](rightSize(keys.len)) + for key in items(keys): result.inc(key) + proc `[]`*[A](t: CountTableRef[A], key: A): int = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. + ## Otherwise ``0`` is returned. + ## + ## See also: + ## * `getOrDefault<#getOrDefault,CountTableRef[A],A,int>`_ to return + ## a custom value if the key doesn't exist + ## * `mget proc<#mget,CountTableRef[A],A>`_ + ## * `[]= proc<#[]%3D,CountTableRef[A],A,int>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,CountTableRef[A],A>`_ for checking if a key + ## is in the table result = t[][key] proc mget*[A](t: CountTableRef[A], key: A): var int = - ## retrieves the value at ``t[key]``. The value can be modified. + ## Retrieves the value at ``t[key]``. The value can be modified. + ## ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. mget(t[], key) -proc getOrDefault*[A](t: CountTableRef[A], key: A): int = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, 0 (the - ## default initialization value of ``int``), is returned. - result = t[].getOrDefault(key) +proc `[]=`*[A](t: CountTableRef[A], key: A, val: int) = + ## Inserts a ``(key, value)`` pair into ``t``. + ## + ## See also: + ## * `[] proc<#[],CountTableRef[A],A>`_ for retrieving a value of a key + ## * `inc proc<#inc,CountTableRef[A],A,int>`_ for incrementing a + ## value of a key + assert val > 0 + t[][key] = val -proc getOrDefault*[A](t: CountTableRef[A], key: A, default: int): int = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, the - ## integer value of ``default`` is returned. - result = t[].getOrDefault(key, default) +proc inc*[A](t: CountTableRef[A], key: A, val = 1) = + ## Increments ``t[key]`` by ``val`` (default: 1). + runnableExamples: + var a = newCountTable("aab") + a.inc('a') + a.inc('b', 10) + doAssert a == newCountTable("aaabbbbbbbbbbb") + t[].inc(key, val) + +proc smallest*[A](t: CountTableRef[A]): (A, int) = + ## Returns the ``(key, value)`` pair with the smallest ``val``. Efficiency: O(n) + ## + ## See also: + ## * `largest proc<#largest,CountTableRef[A]>`_ + t[].smallest + +proc largest*[A](t: CountTableRef[A]): (A, int) = + ## Returns the ``(key, value)`` pair with the largest ``val``. Efficiency: O(n) + ## + ## See also: + ## * `smallest proc<#smallest,CountTable[A]>`_ + t[].largest proc hasKey*[A](t: CountTableRef[A], key: A): bool = - ## returns true iff ``key`` is in the table ``t``. + ## Returns true if ``key`` is in the table ``t``. + ## + ## See also: + ## * `contains proc<#contains,CountTableRef[A],A>`_ for use with the `in` + ## operator + ## * `[] proc<#[],CountTableRef[A],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,CountTableRef[A],A,int>`_ to return + ## a custom value if the key doesn't exist result = t[].hasKey(key) proc contains*[A](t: CountTableRef[A], key: A): bool = - ## Alias of ``hasKey`` for use with the ``in`` operator. + ## Alias of `hasKey proc<#hasKey,CountTableRef[A],A>`_ for use with + ## the ``in`` operator. return hasKey[A](t, key) -proc `[]=`*[A](t: CountTableRef[A], key: A, val: int) = - ## puts a ``(key, value)`` pair into ``t``. ``val`` has to be positive. - assert val > 0 - t[][key] = val +proc getOrDefault*[A](t: CountTableRef[A], key: A, default: int): int = + ## Retrieves the value at ``t[key]`` if``key`` is in ``t``. Otherwise, the + ## integer value of ``default`` is returned. + ## + ## See also: + ## * `[] proc<#[],CountTableRef[A],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,CountTableRef[A],A>`_ for checking if a key + ## is in the table + result = t[].getOrDefault(key, default) -proc inc*[A](t: CountTableRef[A], key: A, val = 1) = - ## increments ``t[key]`` by ``val``. - t[].inc(key, val) +proc len*[A](t: CountTableRef[A]): int = + ## Returns the number of keys in ``t``. + result = t.counter -proc newCountTable*[A](initialSize=64): CountTableRef[A] = - ## creates a new count table that is empty. +proc clear*[A](t: CountTableRef[A]) = + ## Resets the table so that it is empty. + clearImpl() + +proc sort*[A](t: CountTableRef[A]) = + ## Sorts the count table so that the entry with the highest counter comes + ## first. ## - ## ``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 or the ``rightSize`` method in this module. - new(result) - result[] = initCountTable[A](initialSize) + ## **This is destructive! You must not modify `t` afterwards!** + ## + ## You can use the iterators `pairs<#pairs.i,CountTableRef[A]>`_, + ## `keys<#keys.i,CountTableRef[A]>`_, and `values<#values.i,CountTableRef[A]>`_ + ## to iterate over ``t`` in the sorted order. + t[].sort -proc newCountTable*[A](keys: openArray[A]): CountTableRef[A] = - ## creates a new count table with every key in ``keys`` having a count - ## of how many times it occurs in ``keys``. - result = newCountTable[A](rightSize(keys.len)) - for key in items(keys): result.inc(key) +proc merge*[A](s, t: CountTableRef[A]) = + ## Merges the second table into the first one. + runnableExamples: + let + a = newCountTable("aaabbc") + b = newCountTable("bcc") + a.merge(b) + doAssert a == newCountTable("aaabbbccc") + s[].merge(t[]) proc `$`*[A](t: CountTableRef[A]): string = - ## The ``$`` operator for count tables. + ## The ``$`` operator for count tables. Used internally when calling `echo` + ## on a table. dollarImpl() proc `==`*[A](s, t: CountTableRef[A]): bool = - ## The ``==`` operator for count tables. Returns ``true`` iff either both tables - ## are ``nil`` or none is ``nil`` and both contain the same keys with the same + ## The ``==`` operator for count tables. Returns ``true`` if either both tables + ## are ``nil``, or neither is ``nil`` and both contain the same keys with the same ## count. Insert order does not matter. if isNil(s): result = isNil(t) elif isNil(t): result = false else: result = s[] == t[] -proc smallest*[A](t: CountTableRef[A]): (A, int) = - ## returns the ``(key, value)`` pair with the smallest ``val``. Efficiency: O(n) - t[].smallest -proc largest*[A](t: CountTableRef[A]): (A, int) = - ## returns the ``(key, value)`` pair with the largest ``val``. Efficiency: O(n) - t[].largest +iterator pairs*[A](t: CountTableRef[A]): (A, int) = + ## Iterates over any ``(key, value)`` pair in the table ``t``. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,CountTableRef[A]>`_ + ## * `keys iterator<#keys.i,CountTableRef[A]>`_ + ## * `values iterator<#values.i,CountTableRef[A]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## let a = newCountTable("abracadabra") + ## + ## for k, v in pairs(a): + ## echo "key: ", k + ## echo "value: ", v + ## + ## # key: a + ## # value: 5 + ## # key: b + ## # value: 2 + ## # key: c + ## # value: 1 + ## # key: d + ## # value: 1 + ## # key: r + ## # value: 2 + for h in 0..high(t.data): + if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) -proc sort*[A](t: CountTableRef[A]) = - ## sorts the count table so that the entry with the highest counter comes - ## first. This is destructive! You must not modify ``t`` afterwards! - ## You can use the iterators ``pairs``, ``keys``, and ``values`` to iterate over - ## ``t`` in the sorted order. - t[].sort +iterator mpairs*[A](t: CountTableRef[A]): (A, var int) = + ## Iterates over any ``(key, value)`` pair in the table ``t``. The values can + ## be modified. + ## + ## See also: + ## * `pairs iterator<#pairs.i,CountTableRef[A]>`_ + ## * `mvalues iterator<#mvalues.i,CountTableRef[A]>`_ + runnableExamples: + let a = newCountTable("abracadabra") + for k, v in mpairs(a): + v = 2 + doAssert a == newCountTable("aabbccddrr") + for h in 0..high(t.data): + if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) + +iterator keys*[A](t: CountTableRef[A]): A = + ## Iterates over any key in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,CountTable[A]>`_ + ## * `values iterator<#values.i,CountTable[A]>`_ + runnableExamples: + let a = newCountTable("abracadabra") + for k in keys(a): + a[k] = 2 + doAssert a == newCountTable("aabbccddrr") + for h in 0..high(t.data): + if t.data[h].val != 0: yield t.data[h].key + +iterator values*[A](t: CountTableRef[A]): int = + ## Iterates over any value in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,CountTableRef[A]>`_ + ## * `keys iterator<#keys.i,CountTableRef[A]>`_ + ## * `mvalues iterator<#mvalues.i,CountTableRef[A]>`_ + runnableExamples: + let a = newCountTable("abracadabra") + for v in values(a): + assert v < 10 + for h in 0..high(t.data): + if t.data[h].val != 0: yield t.data[h].val + +iterator mvalues*[A](t: CountTableRef[A]): var int = + ## Iterates over any value in the table ``t``. The values can be modified. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,CountTableRef[A]>`_ + ## * `values iterator<#values.i,CountTableRef[A]>`_ + runnableExamples: + var a = newCountTable("abracadabra") + for v in mvalues(a): + v = 2 + doAssert a == newCountTable("aabbccddrr") + for h in 0..high(t.data): + if t.data[h].val != 0: yield t.data[h].val -proc merge*[A](s: var CountTable[A], t: CountTable[A]) = - ## merges the second table into the first one. - for key, value in t: - s.inc(key, value) -proc merge*[A](s, t: CountTable[A]): CountTable[A] = - ## merges the two tables into a new one. - result = initCountTable[A](nextPowerOfTwo(max(s.len, t.len))) - for table in @[s, t]: - for key, value in table: - result.inc(key, value) -proc merge*[A](s, t: CountTableRef[A]) = - ## merges the second table into the first one. - s[].merge(t[]) when isMainModule: type @@ -1302,9 +2710,9 @@ when isMainModule: #test_counttable.nim(7, 43) template/generic instantiation from here #lib/pure/collections/tables.nim(117, 21) template/generic instantiation from here #lib/pure/collections/tableimpl.nim(32, 27) Error: undeclared field: 'hcode - doAssert 0 == t.getOrDefault(testKey) + doAssert 0 == t[testKey] t.inc(testKey, 3) - doAssert 3 == t.getOrDefault(testKey) + doAssert 3 == t[testKey] block: # Clear tests -- cgit 1.4.1-2-gfad0 From 387f481e863996c6791171f34cb05e59e620ce1b Mon Sep 17 00:00:00 2001 From: narimiran Date: Mon, 14 Jan 2019 11:29:31 +0100 Subject: better docs: math --- lib/pure/math.nim | 517 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 420 insertions(+), 97 deletions(-) diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 72abae265..97d1e7987 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -7,15 +7,50 @@ # distribution, for details about the copyright. # -## Constructive mathematics is naturally typed. -- Simon Thompson +## :Author: Nim contributors +## +## *Constructive mathematics is naturally typed.* -- Simon Thompson ## ## Basic math routines for Nim. +## +## Note that the trigonometric functions naturally operate on radians. +## The helper functions `degToRad<#degToRad,T>`_ and `radToDeg<#radToDeg,T>`_ +## provide conversion between radians and degrees. +## +## .. code-block:: +## +## import math +## from sequtils import map +## +## let a = [0.0, PI/6, PI/4, PI/3, PI/2] +## +## echo a.map(sin) +## # @[0.0, 0.499…, 0.707…, 0.866…, 1.0] +## +## echo a.map(tan) +## # @[0.0, 0.577…, 0.999…, 1.732…, 1.633…e+16] +## +## echo cos(degToRad(180.0)) +## # -1.0 +## +## echo sqrt(-1.0) +## # nan (use `complex` module) +## ## This module is available for the `JavaScript target ## `_. ## -## Note that the trigonometric functions naturally operate on radians. -## The helper functions `degToRad` and `radToDeg` provide conversion -## between radians and degrees. +## **See also:** +## * `complex module`_ for complex numbers and their +## mathematical operations +## * `rationals module`_ for rational numbers and their +## mathematical operations +## * `fenv module`_ for handling of floating-point rounding +## and exceptions (overflow, zero-devide, etc.) +## * `random module`_ for fast and tiny random number generator +## * `mersenne module`_ for Mersenne twister random number generator +## * `stats module`_ for statistical analysis +## * `strformat module`_ for formatting floats for print + include "system/inclrtl" {.push debugger:off .} # the user does not want to trace a part @@ -25,9 +60,11 @@ import bitops proc binom*(n, k: int): int {.noSideEffect.} = ## Computes the `binomial coefficient `_. - ## - ## .. code-block:: nim - ## echo binom(6, 2) ## 15 + runnableExamples: + doAssert binom(6, 2) == binom(6, 4) + doAssert binom(6, 2) == 15 + doAssert binom(-6, 2) == 1 + doAssert binom(6, 0) == 1 if k <= 0: return 1 if 2*k > n: return binom(n, n-k) result = n @@ -40,10 +77,15 @@ proc createFactTable[N: static[int]]: array[N, int] = result[i] = result[i - 1] * i proc fac*(n: int): int = - ## Computes the `factorial `_ of a non-negative integer ``n`` + ## Computes the `factorial `_ of + ## a non-negative integer ``n``. ## - ## .. code-block:: nim - ## echo fac(4) ## 24 + ## See also: + ## * `prod proc <#prod,openArray[T]>`_ + runnableExamples: + doAssert fac(3) == 6 + doAssert fac(4) == 24 + doAssert fac(10) == 3628800 const factTable = when sizeof(int) == 4: createFactTable[13]() @@ -59,25 +101,26 @@ when defined(Posix): {.passl: "-lm".} const - PI* = 3.1415926535897932384626433 ## the circle constant PI (Ludolph's number) - TAU* = 2.0 * PI ## the circle constant TAU (= 2 * PI) + PI* = 3.1415926535897932384626433 ## The circle constant PI (Ludolph's number) + TAU* = 2.0 * PI ## The circle constant TAU (= 2 * PI) E* = 2.71828182845904523536028747 ## Euler's number - MaxFloat64Precision* = 16 ## maximum number of meaningful digits + MaxFloat64Precision* = 16 ## Maximum number of meaningful digits ## after the decimal point for Nim's ## ``float64`` type. - MaxFloat32Precision* = 8 ## maximum number of meaningful digits + MaxFloat32Precision* = 8 ## Maximum number of meaningful digits ## after the decimal point for Nim's ## ``float32`` type. - MaxFloatPrecision* = MaxFloat64Precision ## maximum number of + MaxFloatPrecision* = MaxFloat64Precision ## Maximum number of ## meaningful digits ## after the decimal point ## for Nim's ``float`` type. - RadPerDeg = PI / 180.0 ## number of radians per degree + RadPerDeg = PI / 180.0 ## Number of radians per degree type - FloatClass* = enum ## describes the class a floating point value belongs to. - ## This is the type that is returned by `classify`. + FloatClass* = enum ## Describes the class a floating point value belongs to. + ## This is the type that is returned by + ## `classify proc <#classify,float>`_. fcNormal, ## value is an ordinary nonzero floating point value fcSubnormal, ## value is a subnormal (a very small) floating point value fcZero, ## value is zero @@ -87,13 +130,14 @@ type fcNegInf ## value is negative infinity proc classify*(x: float): FloatClass = - ## Classifies a floating point value. Returns ``x``'s class as specified by - ## `FloatClass`. + ## Classifies a floating point value. ## - ## .. code-block:: nim - ## echo classify(0.3) ## fcNormal - ## echo classify(0.0) ## fcZero - ## echo classify(0.3/0.0) ## fcInf + ## Returns ``x``'s class as specified by `FloatClass enum<#FloatClass>`_. + runnableExamples: + doAssert classify(0.3) == fcNormal + doAssert classify(0.0) == fcZero + doAssert classify(0.3/0.0) == fcInf + doAssert classify(-0.3/0.0) == fcNegInf # JavaScript and most C compilers have no classify: if x == 0.0: @@ -110,20 +154,30 @@ proc classify*(x: float): FloatClass = proc isPowerOfTwo*(x: int): bool {.noSideEffect.} = ## Returns ``true``, if ``x`` is a power of two, ``false`` otherwise. + ## ## Zero and negative numbers are not a power of two. ## - ## .. code-block:: nim - ## echo isPowerOfTwo(5) ## false - ## echo isPowerOfTwo(8) ## true + ## See also: + ## * `nextPowerOfTwo proc<#nextPowerOfTwo,int>`_ + runnableExamples: + doAssert isPowerOfTwo(16) == true + doAssert isPowerOfTwo(5) == false + doAssert isPowerOfTwo(0) == false + doAssert isPowerOfTwo(-16) == false return (x > 0) and ((x and (x - 1)) == 0) proc nextPowerOfTwo*(x: int): int {.noSideEffect.} = ## Returns ``x`` rounded up to the nearest power of two. + ## ## Zero and negative numbers get rounded up to 1. ## - ## .. code-block:: nim - ## echo nextPowerOfTwo(8) ## 8 - ## echo nextPowerOfTwo(9) ## 16 + ## See also: + ## * `isPowerOfTwo proc<#isPowerOfTwo,int>`_ + runnableExamples: + doAssert nextPowerOfTwo(16) == 16 + doAssert nextPowerOfTwo(5) == 8 + doAssert nextPowerOfTwo(0) == 1 + doAssert nextPowerOfTwo(-16) == 1 result = x - 1 when defined(cpu64): result = result or (result shr 32) @@ -138,9 +192,12 @@ proc nextPowerOfTwo*(x: int): int {.noSideEffect.} = proc countBits32*(n: int32): int {.noSideEffect.} = ## Counts the set bits in ``n``. - ## - ## .. code-block:: nim - ## echo countBits32(13'i32) ## 3 + runnableExamples: + doAssert countBits32(7) == 3 + doAssert countBits32(8) == 1 + doAssert countBits32(15) == 4 + doAssert countBits32(16) == 1 + doAssert countBits32(17) == 2 var v = n v = v -% ((v shr 1'i32) and 0x55555555'i32) v = (v and 0x33333333'i32) +% ((v shr 2'i32) and 0x33333333'i32) @@ -148,37 +205,58 @@ proc countBits32*(n: int32): int {.noSideEffect.} = proc sum*[T](x: openArray[T]): T {.noSideEffect.} = ## Computes the sum of the elements in ``x``. + ## ## If ``x`` is empty, 0 is returned. ## - ## .. code-block:: nim - ## echo sum([1.0, 2.5, -3.0, 4.3]) ## 4.8 + ## See also: + ## * `prod proc <#prod,openArray[T]>`_ + ## * `cumsum proc <#cumsum,openArray[T]>`_ + ## * `cumsummed proc <#cumsummed,openArray[T]>`_ + runnableExamples: + doAssert sum([1, 2, 3, 4]) == 10 + doAssert sum([-1.5, 2.7, -0.1]) == 1.1 for i in items(x): result = result + i proc prod*[T](x: openArray[T]): T {.noSideEffect.} = ## Computes the product of the elements in ``x``. + ## ## If ``x`` is empty, 1 is returned. ## - ## .. code-block:: nim - ## echo prod([1.0, 3.0, -0.2]) ## -0.6 + ## See also: + ## * `sum proc <#sum,openArray[T]>`_ + ## * `fac proc <#fac,int>`_ + runnableExamples: + doAssert prod([1, 2, 3, 4]) == 24 + doAssert prod([-4, 3, 5]) == -60 result = 1.T for i in items(x): result = result * i proc cumsummed*[T](x: openArray[T]): seq[T] = - ## Return cumulative aka prefix summation of ``x``. + ## Return cumulative (aka prefix) summation of ``x``. ## - ## .. code-block:: nim - ## var x = [1, 2, 3, 4] - ## echo x.cumsummed # [1, 3, 6, 10] + ## See also: + ## * `sum proc <#sum,openArray[T]>`_ + ## * `cumsum proc <#cumsum,openArray[T]>`_ for the in-place version + ## * `cumsummed proc <#cumsummed,openArray[T]>`_ + runnableExamples: + let a = [1, 2, 3, 4] + doAssert cumsummed(a) == @[1, 3, 6, 10] result.setLen(x.len) result[0] = x[0] for i in 1 ..< x.len: result[i] = result[i-1] + x[i] proc cumsum*[T](x: var openArray[T]) = - ## Transforms ``x`` in-place into its cumulative aka prefix summation. + ## Transforms ``x`` in-place (must be declared as `var`) into its + ## cumulative (aka prefix) summation. ## - ## .. code-block:: nim - ## var x = [1, 2, 3, 4] - ## x.cumsum; echo x # [1, 3, 6, 10] + ## See also: + ## * `sum proc <#sum,openArray[T]>`_ + ## * `cumsummed proc <#cumsummed,openArray[T]>`_ for a version which + ## returns cumsummed sequence + runnableExamples: + var a = [1, 2, 3, 4] + cumsum(a) + doAssert a == @[1, 3, 6, 10] for i in 1 ..< x.len: x[i] = x[i-1] + x[i] {.push noSideEffect.} @@ -187,20 +265,40 @@ when not defined(JS): # C proc sqrt*(x: float64): float64 {.importc: "sqrt", header: "".} ## Computes the square root of ``x``. ## + ## See also: + ## * `cbrt proc <#cbrt,float64>`_ for cubic root + ## ## .. code-block:: nim + ## echo sqrt(4.0) ## 2.0 ## echo sqrt(1.44) ## 1.2 + ## echo sqrt(-4.0) ## nan proc cbrt*(x: float32): float32 {.importc: "cbrtf", header: "".} proc cbrt*(x: float64): float64 {.importc: "cbrt", header: "".} ## Computes the cubic root of ``x``. ## + ## See also: + ## * `sqrt proc <#sqrt,float64>`_ for square root + ## ## .. code-block:: nim + ## echo cbrt(8.0) ## 2.0 ## echo cbrt(2.197) ## 1.3 + ## echo cbrt(-27.0) ## -3.0 proc ln*(x: float32): float32 {.importc: "logf", header: "".} proc ln*(x: float64): float64 {.importc: "log", header: "".} - ## Computes the `natural logarithm `_ of ``x``. + ## Computes the `natural logarithm `_ + ## of ``x``. + ## + ## See also: + ## * `log proc <#log,T,T>`_ + ## * `log10 proc <#log10,float64>`_ + ## * `log2 proc <#log2,float64>`_ + ## * `exp proc <#exp,float64>`_ ## ## .. code-block:: nim ## echo ln(exp(4.0)) ## 4.0 + ## echo ln(1.0)) ## 0.0 + ## echo ln(0.0) ## -inf + ## echo ln(-7.0) ## nan else: # JS proc sqrt*(x: float32): float32 {.importc: "Math.sqrt", nodecl.} proc sqrt*(x: float64): float64 {.importc: "Math.sqrt", nodecl.} @@ -211,8 +309,18 @@ else: # JS proc log*[T: SomeFloat](x, base: T): T = ## Computes the logarithm of ``x`` to base ``base``. ## + ## See also: + ## * `ln proc <#ln,float64>`_ + ## * `log10 proc <#log10,float64>`_ + ## * `log2 proc <#log2,float64>`_ + ## * `exp proc <#exp,float64>`_ + ## ## .. code-block:: nim - ## echo log(9.0, 3.0) ## 2.0 + ## echo log(9.0, 3.0) ## 2.0 + ## echo log(32.0, 2.0) ## 5.0 + ## echo log(0.0, 2.0) ## -inf + ## echo log(-7.0, 4.0) ## nan + ## echo log(8.0, -2.0) ## nan ln(x) / ln(base) when not defined(JS): # C @@ -220,77 +328,164 @@ when not defined(JS): # C proc log10*(x: float64): float64 {.importc: "log10", header: "".} ## Computes the common logarithm (base 10) of ``x``. ## + ## See also: + ## * `ln proc <#ln,float64>`_ + ## * `log proc <#log,T,T>`_ + ## * `log2 proc <#log2,float64>`_ + ## * `exp proc <#exp,float64>`_ + ## ## .. code-block:: nim - ## echo log10(100.0) ## 2.0 + ## echo log10(100.0) ## 2.0 + ## echo log10(0.0) ## nan + ## echo log10(-100.0) ## -inf proc exp*(x: float32): float32 {.importc: "expf", header: "".} proc exp*(x: float64): float64 {.importc: "exp", header: "".} - ## Computes the exponential function of ``x`` (pow(E, x)). + ## Computes the exponential function of ``x`` (e^x). + ## + ## See also: + ## * `ln proc <#ln,float64>`_ + ## * `log proc <#log,T,T>`_ + ## * `log10 proc <#log10,float64>`_ + ## * `log2 proc <#log2,float64>`_ ## ## .. code-block:: nim - ## echo exp(1.0) ## 2.718281828459045 + ## echo exp(1.0) ## 2.718281828459045 ## echo ln(exp(4.0)) ## 4.0 + ## echo exp(0.0) ## 1.0 + ## echo exp(-1.0) ## 0.3678794411714423 proc sin*(x: float32): float32 {.importc: "sinf", header: "".} proc sin*(x: float64): float64 {.importc: "sin", header: "".} ## Computes the sine of ``x``. ## + ## See also: + ## * `cos proc <#cos,float64>`_ + ## * `tan proc <#tan,float64>`_ + ## * `arcsin proc <#arcsin,float64>`_ + ## * `sinh proc <#sinh,float64>`_ + ## ## .. code-block:: nim - ## echo sin(PI / 6) ## 0.4999999999999999 + ## echo sin(PI / 6) ## 0.4999999999999999 ## echo sin(degToRad(90.0)) ## 1.0 proc cos*(x: float32): float32 {.importc: "cosf", header: "".} proc cos*(x: float64): float64 {.importc: "cos", header: "".} ## Computes the cosine of ``x``. ## + ## See also: + ## * `sin proc <#sin,float64>`_ + ## * `tan proc <#tan,float64>`_ + ## * `arccos proc <#arccos,float64>`_ + ## * `cosh proc <#cosh,float64>`_ + ## ## .. code-block:: nim - ## echo cos(2 * PI) ## 1.0 + ## echo cos(2 * PI) ## 1.0 ## echo cos(degToRad(60.0)) ## 0.5000000000000001 proc tan*(x: float32): float32 {.importc: "tanf", header: "".} proc tan*(x: float64): float64 {.importc: "tan", header: "".} ## Computes the tangent of ``x``. ## + ## See also: + ## * `sin proc <#sin,float64>`_ + ## * `cos proc <#cos,float64>`_ + ## * `arctan proc <#arctan,float64>`_ + ## * `tanh proc <#tanh,float64>`_ + ## ## .. code-block:: nim ## echo tan(degToRad(45.0)) ## 0.9999999999999999 - ## echo tan(PI / 4) ## 0.9999999999999999 + ## echo tan(PI / 4) ## 0.9999999999999999 proc sinh*(x: float32): float32 {.importc: "sinhf", header: "".} proc sinh*(x: float64): float64 {.importc: "sinh", header: "".} ## Computes the `hyperbolic sine `_ of ``x``. ## + ## See also: + ## * `cosh proc <#cosh,float64>`_ + ## * `tanh proc <#tanh,float64>`_ + ## * `arcsinh proc <#arcsinh,float64>`_ + ## * `sin proc <#sin,float64>`_ + ## ## .. code-block:: nim - ## echo sinh(1.0) ## 1.175201193643801 + ## echo sinh(0.0) ## 0.0 + ## echo sinh(1.0) ## 1.175201193643801 + ## echo sinh(degToRad(90.0)) ## 2.301298902307295 proc cosh*(x: float32): float32 {.importc: "coshf", header: "".} proc cosh*(x: float64): float64 {.importc: "cosh", header: "".} ## Computes the `hyperbolic cosine `_ of ``x``. ## + ## See also: + ## * `sinh proc <#sinh,float64>`_ + ## * `tanh proc <#tanh,float64>`_ + ## * `arccosh proc <#arccosh,float64>`_ + ## * `cos proc <#cos,float64>`_ + ## ## .. code-block:: nim - ## echo cosh(1.0) ## 1.543080634815244 + ## echo cosh(0.0) ## 1.0 + ## echo cosh(1.0) ## 1.543080634815244 + ## echo cosh(degToRad(90.0)) ## 2.509178478658057 proc tanh*(x: float32): float32 {.importc: "tanhf", header: "".} proc tanh*(x: float64): float64 {.importc: "tanh", header: "".} ## Computes the `hyperbolic tangent `_ of ``x``. ## + ## See also: + ## * `sinh proc <#sinh,float64>`_ + ## * `cosh proc <#cosh,float64>`_ + ## * `arctanh proc <#arctanh,float64>`_ + ## * `tan proc <#tan,float64>`_ + ## ## .. code-block:: nim - ## echo tanh(1.0) ## 0.7615941559557649 + ## echo tanh(0.0) ## 0.0 + ## echo tanh(1.0) ## 0.7615941559557649 + ## echo tanh(degToRad(90.0)) ## 0.9171523356672744 proc arccos*(x: float32): float32 {.importc: "acosf", header: "".} proc arccos*(x: float64): float64 {.importc: "acos", header: "".} ## Computes the arc cosine of ``x``. ## + ## See also: + ## * `arcsin proc <#arcsin,float64>`_ + ## * `arctan proc <#arctan,float64>`_ + ## * `arctan2 proc <#arctan2,float64,float64>`_ + ## * `cos proc <#cos,float64>`_ + ## ## .. code-block:: nim - ## echo arccos(1.0) ## 0.0 + ## echo radToDeg(arccos(0.0)) ## 90.0 + ## echo radToDeg(arccos(1.0)) ## 0.0 proc arcsin*(x: float32): float32 {.importc: "asinf", header: "".} proc arcsin*(x: float64): float64 {.importc: "asin", header: "".} ## Computes the arc sine of ``x``. + ## + ## See also: + ## * `arccos proc <#arccos,float64>`_ + ## * `arctan proc <#arctan,float64>`_ + ## * `arctan2 proc <#arctan2,float64,float64>`_ + ## * `sin proc <#sin,float64>`_ + ## + ## .. code-block:: nim + ## echo radToDeg(arcsin(0.0)) ## 0.0 + ## echo radToDeg(arcsin(1.0)) ## 90.0 proc arctan*(x: float32): float32 {.importc: "atanf", header: "".} proc arctan*(x: float64): float64 {.importc: "atan", header: "".} ## Calculate the arc tangent of ``x``. ## + ## See also: + ## * `arcsin proc <#arcsin,float64>`_ + ## * `arccos proc <#arccos,float64>`_ + ## * `arctan2 proc <#arctan2,float64,float64>`_ + ## * `tan proc <#tan,float64>`_ + ## ## .. code-block:: nim ## echo arctan(1.0) ## 0.7853981633974483 ## echo radToDeg(arctan(1.0)) ## 45.0 proc arctan2*(y, x: float32): float32 {.importc: "atan2f", header: "".} proc arctan2*(y, x: float64): float64 {.importc: "atan2", header: "".} ## Calculate the arc tangent of ``y`` / ``x``. - ## `arctan2` returns the arc tangent of ``y`` / ``x``; it produces correct - ## results even when the resulting angle is near pi/2 or -pi/2 - ## (``x`` near 0). + ## + ## It produces correct results even when the resulting angle is near + ## pi/2 or -pi/2 (``x`` near 0). + ## + ## See also: + ## * `arcsin proc <#arcsin,float64>`_ + ## * `arccos proc <#arccos,float64>`_ + ## * `arctan proc <#arctan,float64>`_ + ## * `tan proc <#tan,float64>`_ ## ## .. code-block:: nim ## echo arctan2(1.0, 0.0) ## 1.570796326794897 @@ -331,18 +526,18 @@ else: # JS proc arctanh*[T: float32|float64](x: T): T {.importc: "Math.atanh", nodecl.} proc cot*[T: float32|float64](x: T): T = 1.0 / tan(x) - ## Computes the cotangent of ``x``. + ## Computes the cotangent of ``x`` (1 / tan(x)). proc sec*[T: float32|float64](x: T): T = 1.0 / cos(x) - ## Computes the secant of ``x``. + ## Computes the secant of ``x`` (1 / cos(x)). proc csc*[T: float32|float64](x: T): T = 1.0 / sin(x) - ## Computes the cosecant of ``x``. + ## Computes the cosecant of ``x`` (1 / sin(x)). proc coth*[T: float32|float64](x: T): T = 1.0 / tanh(x) - ## Computes the hyperbolic cotangent of ``x``. + ## Computes the hyperbolic cotangent of ``x`` (1 / tanh(x)). proc sech*[T: float32|float64](x: T): T = 1.0 / cosh(x) - ## Computes the hyperbolic secant of ``x``. + ## Computes the hyperbolic secant of ``x`` (1 / cosh(x)). proc csch*[T: float32|float64](x: T): T = 1.0 / sinh(x) - ## Computes the hyperbolic cosecant of ``x``. + ## Computes the hyperbolic cosecant of ``x`` (1 / sinh(x)). proc arccot*[T: float32|float64](x: T): T = arctan(1.0 / x) ## Computes the inverse cotangent of ``x``. @@ -370,11 +565,17 @@ when not defined(JS): # C ## echo hypot(4.0, 3.0) ## 5.0 proc pow*(x, y: float32): float32 {.importc: "powf", header: "".} proc pow*(x, y: float64): float64 {.importc: "pow", header: "".} - ## computes x to power raised of y. + ## Computes x to power raised of y. + ## + ## To compute power between integers (e.g. 2^6), use `^ proc<#^,T,Natural>`_. ## - ## To compute power between integers, use ``^`` e.g. 2 ^ 6 + ## See also: + ## * `^ proc<#^,T,Natural>`_ + ## * `sqrt proc <#sqrt,float64>`_ + ## * `cbrt proc <#cbrt,float64>`_ ## ## .. code-block:: nim + ## echo pow(100, 1.5) ## 1000.0 ## echo pow(16.0, 0.5) ## 4.0 # TODO: add C89 version on windows @@ -388,6 +589,15 @@ when not defined(JS): # C proc gamma*(x: float32): float32 {.importc: "tgammaf", header: "".} proc gamma*(x: float64): float64 {.importc: "tgamma", header: "".} ## Computes the the `gamma function `_ for ``x``. + ## + ## See also: + ## * `lgamma proc <#lgamma,float64>`_ for a natural log of gamma function + ## + ## .. code-block:: Nim + ## echo gamma(1.0) # 1.0 + ## echo gamma(4.0) # 6.0 + ## echo gamma(11.0) # 3628800.0 + ## echo gamma(-1.0) # nan proc tgamma*(x: float32): float32 {.deprecated: "use gamma instead", importc: "tgammaf", header: "".} proc tgamma*(x: float64): float64 @@ -397,19 +607,43 @@ when not defined(JS): # C proc lgamma*(x: float32): float32 {.importc: "lgammaf", header: "".} proc lgamma*(x: float64): float64 {.importc: "lgamma", header: "".} ## Computes the natural log of the gamma function for ``x``. + ## + ## See also: + ## * `gamma proc <#gamma,float64>`_ for gamma function + ## + ## .. code-block:: Nim + ## echo lgamma(1.0) # 1.0 + ## echo lgamma(4.0) # 1.791759469228055 + ## echo lgamma(11.0) # 15.10441257307552 + ## echo lgamma(-1.0) # inf proc floor*(x: float32): float32 {.importc: "floorf", header: "".} proc floor*(x: float64): float64 {.importc: "floor", header: "".} ## Computes the floor function (i.e., the largest integer not greater than ``x``). ## + ## See also: + ## * `ceil proc <#ceil,float64>`_ + ## * `round proc <#round,float64>`_ + ## * `trunc proc <#trunc,float64>`_ + ## ## .. code-block:: nim + ## echo floor(2.1) ## 2.0 + ## echo floor(2.9) ## 2.0 ## echo floor(-3.5) ## -4.0 proc ceil*(x: float32): float32 {.importc: "ceilf", header: "".} proc ceil*(x: float64): float64 {.importc: "ceil", header: "".} - ## Computes the ceiling function (i.e., the smallest integer not less than ``x``). + ## Computes the ceiling function (i.e., the smallest integer not smaller + ## than ``x``). + ## + ## See also: + ## * `floor proc <#floor,float64>`_ + ## * `round proc <#round,float64>`_ + ## * `trunc proc <#trunc,float64>`_ ## ## .. code-block:: nim + ## echo ceil(2.1) ## 3.0 + ## echo ceil(2.9) ## 3.0 ## echo ceil(-2.1) ## -2.0 when windowsCC89: @@ -470,26 +704,50 @@ when not defined(JS): # C else: proc round*(x: float32): float32 {.importc: "roundf", header: "".} proc round*(x: float64): float64 {.importc: "round", header: "".} - ## Rounds a float to zero decimal places. Used internally by the round - ## function when the specified number of places is 0. + ## Rounds a float to zero decimal places. + ## + ## Used internally by the `round proc <#round,T,int>`_ + ## when the specified number of places is 0. + ## + ## See also: + ## * `round proc <#round,T,int>`_ for rounding to the specific + ## number of decimal places + ## * `floor proc <#floor,float64>`_ + ## * `ceil proc <#ceil,float64>`_ + ## * `trunc proc <#trunc,float64>`_ + ## + ## .. code-block:: nim + ## echo round(3.4) ## 3.0 + ## echo round(3.5) ## 4.0 + ## echo round(4.5) ## 5.0 proc trunc*(x: float32): float32 {.importc: "truncf", header: "".} proc trunc*(x: float64): float64 {.importc: "trunc", header: "".} ## Truncates ``x`` to the decimal point. ## + ## See also: + ## * `floor proc <#floor,float64>`_ + ## * `ceil proc <#ceil,float64>`_ + ## * `round proc <#round,float64>`_ + ## ## .. code-block:: nim ## echo trunc(PI) # 3.0 ## echo trunc(-1.85) # -1.0 proc fmod*(x, y: float32): float32 {.deprecated: "use mod instead", importc: "fmodf", header: "".} proc fmod*(x, y: float64): float64 {.deprecated: "use mod instead", importc: "fmod", header: "".} + ## **Deprecated since version 0.19.0**: Use the `mod proc + ## <#mod,float64,float64>`_ instead. + ## ## Computes the remainder of ``x`` divided by ``y``. - ## **Deprecated since version 0.19.0**: Use the ``mod`` operator instead. proc `mod`*(x, y: float32): float32 {.importc: "fmodf", header: "".} proc `mod`*(x, y: float64): float64 {.importc: "fmod", header: "".} ## Computes the modulo operation for float values (the remainder of ``x`` divided by ``y``). ## + ## See also: + ## * `floorMod proc <#floorMod,T,T>`_ for Python-like (% operator) behavior + ## ## .. code-block:: nim ## ( 6.5 mod 2.5) == 1.5 ## (-6.5 mod 2.5) == -1.5 @@ -520,16 +778,22 @@ else: # JS ## (-6.5 mod -2.5) == -1.5 proc round*[T: float32|float64](x: T, places: int): T {.deprecated: "use format instead".} = + ## **Deprecated:** use `strformat module `_ + ## ## Decimal rounding on a binary floating point number. ## ## This function is NOT reliable. Floating point numbers cannot hold - ## non integer decimals precisely. If ``places`` is 0 (or omitted), + ## non integer decimals precisely. If ``places`` is 0 (or omitted), ## round to the nearest integral value following normal mathematical - ## rounding rules (e.g. ``round(54.5) -> 55.0``). If ``places`` is + ## rounding rules (e.g. ``round(54.5) -> 55.0``). If ``places`` is ## greater than 0, round to the given number of decimal places, - ## e.g. ``round(54.346, 2) -> 54.350000000000001421...``. If ``places`` is negative, round - ## to the left of the decimal place, e.g. ``round(537.345, -1) -> + ## e.g. ``round(54.346, 2) -> 54.350000000000001421…``. If ``places`` is negative, round + ## to the left of the decimal place, e.g. ``round(537.345, -1) -> ## 540.0`` + ## + ## .. code-block:: Nim + ## echo round(PI, 2) ## 3.14 + ## echo round(PI, 4) ## 3.1416 if places == 0: result = round(x) else: @@ -538,9 +802,14 @@ proc round*[T: float32|float64](x: T, places: int): T {.deprecated: "use format proc floorDiv*[T: SomeInteger](x, y: T): T = ## Floor division is conceptually defined as ``floor(x / y)``. - ## This is different from the ``div`` operator, which is defined - ## as ``trunc(x / y)``. That is, ``div`` rounds towards ``0`` and ``floorDiv`` - ## rounds down. + ## + ## This is different from the `system.div `_ + ## operator, which is defined as ``trunc(x / y)``. + ## That is, ``div`` rounds towards ``0`` and ``floorDiv`` rounds down. + ## + ## See also: + ## * `system.div proc `_ for integer division + ## * `floorMod proc <#floorMod,T,T>`_ for Python-like (% operator) behavior ## ## .. code-block:: nim ## echo floorDiv( 13, 3) # 4 @@ -553,8 +822,13 @@ proc floorDiv*[T: SomeInteger](x, y: T): T = proc floorMod*[T: SomeNumber](x, y: T): T = ## Floor modulus is conceptually defined as ``x - (floorDiv(x, y) * y)``. + ## ## This proc behaves the same as the ``%`` operator in Python. ## + ## See also: + ## * `mod proc <#mod,float64,float64>`_ + ## * `floorDiv proc <#floorDiv,T,T>`_ + ## ## .. code-block:: nim ## echo floorMod( 13, 3) # 1 ## echo floorMod(-13, 3) # 2 @@ -570,6 +844,7 @@ when not defined(JS): importc: "frexp", header: "".} proc frexp*[T, U](x: T, exponent: var U): T = ## Split a number into mantissa and exponent. + ## ## ``frexp`` calculates the mantissa m (a float greater than or equal to 0.5 ## and less than 1) and the integer value n such that ``x`` (the original ## float value) equals ``m * 2**n``. frexp stores n in `exponent` and returns @@ -602,7 +877,19 @@ when not defined(JS): else: proc log2*(x: float32): float32 {.importc: "log2f", header: "".} proc log2*(x: float64): float64 {.importc: "log2", header: "".} - ## Computes the binary logarithm (base 2) of ``x`` + ## Computes the binary logarithm (base 2) of ``x``. + ## + ## See also: + ## * `log proc <#log,T,T>`_ + ## * `log10 proc <#log10,float64>`_ + ## * `ln proc <#ln,float64>`_ + ## * `exp proc <#exp,float64>`_ + ## + ## .. code-block:: Nim + ## echo log2(8.0) # 3.0 + ## echo log2(1.0) # 0.0 + ## echo log2(0.0) # -inf + ## echo log2(-2.0) # nan else: proc frexp*[T: float32|float64](x: T, exponent: var int): T = @@ -631,7 +918,8 @@ proc splitDecimal*[T: float32|float64](x: T): tuple[intpart: T, floatpart: T] = ## function in C. ## ## .. code-block:: nim - ## echo splitDecimal(5.25) # (intpart: 5.0, floatpart: 0.25) + ## echo splitDecimal(5.25) # (intpart: 5.0, floatpart: 0.25) + ## echo splitDecimal(-2.73) # (intpart: -2.0, floatpart: -0.73) var absolute: T absolute = abs(x) @@ -644,26 +932,36 @@ proc splitDecimal*[T: float32|float64](x: T): tuple[intpart: T, floatpart: T] = {.pop.} proc degToRad*[T: float32|float64](d: T): T {.inline.} = - ## Convert from degrees to radians + ## Convert from degrees to radians. + ## + ## See also: + ## * `radToDeg proc <#radToDeg,T>`_ ## ## .. code-block:: nim ## echo degToRad(180.0) # 3.141592653589793 result = T(d) * RadPerDeg proc radToDeg*[T: float32|float64](d: T): T {.inline.} = - ## Convert from radians to degrees - + ## Convert from radians to degrees. + ## + ## See also: + ## * `degToRad proc <#degToRad,T>`_ + ## ## .. code-block:: nim ## echo degToRad(2 * PI) # 360.0 result = T(d) / RadPerDeg proc sgn*[T: SomeNumber](x: T): int {.inline.} = - ## Sign function. Returns -1 for negative numbers and ``NegInf``, 1 for - ## positive numbers and ``Inf``, and 0 for positive zero, negative zero and - ## ``NaN``. + ## Sign function. + ## + ## Returns: + ## * `-1` for negative numbers and ``NegInf``, + ## * `1` for positive numbers and ``Inf``, + ## * `0` for positive zero, negative zero and ``NaN`` ## ## .. code-block:: nim - ## echo sgn(-5) # 1 + ## echo sgn(5) # 1 + ## echo sgn(0) # 0 ## echo sgn(-4.1) # -1 ord(T(0) < x) - ord(x < T(0)) @@ -671,11 +969,20 @@ proc sgn*[T: SomeNumber](x: T): int {.inline.} = {.pop.} proc `^`*[T](x: T, y: Natural): T = - ## Computes ``x`` to the power ``y``. ``x`` must be non-negative, use - ## `pow <#pow,float,float>`_ for negative exponents. + ## Computes ``x`` to the power ``y``. + ## + ## Exponent ``y`` must be non-negative, use + ## `pow proc <#pow,float64,float64>`_ for negative exponents. + ## + ## See also: + ## * `pow proc <#pow,float64,float64>`_ for negative exponent or + ## floats + ## * `sqrt proc <#sqrt,float64>`_ + ## * `cbrt proc <#cbrt,float64>`_ ## ## .. code-block:: nim - ## echo 2 ^ 3 # 8 + ## echo 2^3 # 8 + ## echo -2^3 # -8 when compiles(y >= T(0)): assert y >= T(0) else: @@ -693,9 +1000,16 @@ proc `^`*[T](x: T, y: Natural): T = proc gcd*[T](x, y: T): T = ## Computes the greatest common (positive) divisor of ``x`` and ``y``. + ## ## Note that for floats, the result cannot always be interpreted as ## "greatest decimal `z` such that ``z*N == x and z*M == y`` ## where N and M are positive integers." + ## + ## See also: + ## * `gcd proc <#gcd,SomeInteger,SomeInteger>`_ for integer version + ## * `lcm proc <#lcm,T,T>`_ + runnableExamples: + doAssert gcd(13.5, 9.0) == 4.5 var (x, y) = (x, y) while y != 0: x = x mod y @@ -703,11 +1017,15 @@ proc gcd*[T](x, y: T): T = abs x proc gcd*(x, y: SomeInteger): SomeInteger = - ## Computes the greatest common (positive) divisor of ``x`` and ``y``. - ## Using binary GCD (aka Stein's) algorithm. + ## Computes the greatest common (positive) divisor of ``x`` and ``y``, + ## using binary GCD (aka Stein's) algorithm. ## - ## .. code-block:: nim - ## echo gcd(24, 30) # 6 + ## See also: + ## * `gcd proc <#gcd,T,T>`_ for floats version + ## * `lcm proc <#lcm,T,T>`_ + runnableExamples: + doAssert gcd(12, 8) == 4 + doAssert gcd(17, 63) == 1 when x is SomeSignedInt: var x = abs(x) else: @@ -734,10 +1052,15 @@ proc gcd*(x, y: SomeInteger): SomeInteger = proc lcm*[T](x, y: T): T = ## Computes the least common multiple of ``x`` and ``y``. ## - ## .. code-block:: nim - ## echo lcm(24, 30) # 120 + ## See also: + ## * `gcd proc <#gcd,T,T>`_ + runnableExamples: + doAssert lcm(24, 30) == 120 + doAssert lcm(13, 39) == 39 x div gcd(x, y) * y + + when isMainModule and not defined(JS) and not windowsCC89: # Check for no side effect annotation proc mySqrt(num: float): float {.noSideEffect.} = -- cgit 1.4.1-2-gfad0 From b8454327c52faa82632cc90dd8fa326efbb38565 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 16 Jan 2019 01:16:14 -0800 Subject: json: support tuple (#10010) --- lib/pure/json.nim | 17 ++++++++++++++++- tests/stdlib/tjsonmacro.nim | 4 ++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 010dd8f70..02fa6dc67 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -142,7 +142,8 @@ runnableExamples: doAssert $(%* Foo()) == """{"a1":0,"a2":0,"a0":0,"a3":0,"a4":0}""" import - hashes, tables, strutils, lexbase, streams, unicode, macros, parsejson + hashes, tables, strutils, lexbase, streams, unicode, macros, parsejson, + typetraits export tables.`$` @@ -356,6 +357,20 @@ when false: assert false notin elements, "usage error: only empty sets allowed" assert true notin elements, "usage error: only empty sets allowed" +#[ +Note: could use simply: +proc `%`*(o: object|tuple): JsonNode +but blocked by https://github.com/nim-lang/Nim/issues/10019 +]# +proc `%`*(o: tuple): JsonNode = + ## Generic constructor for JSON data. Creates a new `JObject JsonNode` + when isNamedTuple(type(o)): + result = newJObject() + for k, v in o.fieldPairs: result[k] = %v + else: + result = newJArray() + for a in o.fields: result.add(%a) + proc `%`*(o: object): JsonNode = ## Generic constructor for JSON data. Creates a new `JObject JsonNode` result = newJObject() diff --git a/tests/stdlib/tjsonmacro.nim b/tests/stdlib/tjsonmacro.nim index 33332447b..2e95b4833 100644 --- a/tests/stdlib/tjsonmacro.nim +++ b/tests/stdlib/tjsonmacro.nim @@ -516,3 +516,7 @@ when true: var w = u.to(MyDistRef) doAssert v.name == "smith" doAssert MyRef(w).name == "smith" + + block test_tuple: + doAssert $(%* (a1: 10, a2: "foo")) == """{"a1":10,"a2":"foo"}""" + doAssert $(%* (10, "foo")) == """[10,"foo"]""" -- cgit 1.4.1-2-gfad0 From 3abe6e9e14e1da2890ae7b20f0f2bee247b3920b Mon Sep 17 00:00:00 2001 From: narimiran Date: Wed, 16 Jan 2019 11:58:51 +0100 Subject: quickfix for sequtils docs [ci skip] --- lib/pure/collections/sequtils.nim | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 23ac0902a..b562e2ac6 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -136,12 +136,9 @@ proc count*[T](s: openArray[T], x: T): int = let a = @[1, 2, 2, 3, 2, 4, 2] b = "abracadabra" - c = count(a, 2) - d = count(a, 99) - e = count(b, 'r') - assert c == 4 - assert d == 0 - assert e == 2 + assert count(a, 2) == 4 + assert count(a, 99) == 0 + assert count(b, 'r') == 2 for itm in items(s): if itm == x: -- cgit 1.4.1-2-gfad0 From 2e8bf8861417d742a1f1fe7e695a2c10a039d5d6 Mon Sep 17 00:00:00 2001 From: narimiran Date: Wed, 16 Jan 2019 12:32:00 +0100 Subject: sequtils doc: another quickfix [ci skip] --- lib/pure/collections/sequtils.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index b562e2ac6..f73d37f47 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -301,7 +301,7 @@ proc map*[T, S](s: openArray[T], op: proc (x: T): S {.closure.}): ## ## See also: ## * `mapIt template<#mapIt.t,typed,untyped>`_ - ## * `apply proc<#apply,openArray[T],proc(T)_2>`_ for the in-ace version + ## * `apply proc<#apply,openArray[T],proc(T)_2>`_ for the in-place version ## runnableExamples: let @@ -380,7 +380,7 @@ proc filter*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): seq[T] ## See also: ## * `filterIt template<#filterIt.t,untyped,untyped>`_ ## * `filter iterator<#filter.i,openArray[T],proc(T)>`_ - ## * `keepIf proc<#keepIf,seq[T],proc(T)>`_ for the in-ace version + ## * `keepIf proc<#keepIf,seq[T],proc(T)>`_ for the in-place version ## runnableExamples: let @@ -811,7 +811,7 @@ template mapIt*(s: typed, op: untyped): untyped = ## ## See also: ## * `map proc<#map,openArray[T],proc(T)>`_ - ## * `applyIt template<#applyIt.t,untyped,untyped>`_ for the in-ace version + ## * `applyIt template<#applyIt.t,untyped,untyped>`_ for the in-place version ## runnableExamples: let -- cgit 1.4.1-2-gfad0 From 1cb5e20620690044d158e419855582bc116003ba Mon Sep 17 00:00:00 2001 From: Araq Date: Wed, 16 Jan 2019 10:05:33 +0100 Subject: parseutils.nim: help the codegen produce better code --- lib/pure/parseutils.nim | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index 36fcb1965..ee8453b21 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -95,7 +95,7 @@ proc parseOct*(s: string, number: var int, start = 0, maxLen = 0): int {. inc(i) if foundDigit: result = i-start -proc parseBin*(s: string, number: var int, start = 0, maxLen = 0): int {. +proc parseBin*(s: string, number: var int, start = 0, maxLen = 0): int {. rtl, extern: "npuParseBin", noSideEffect.} = ## Parses an binary number and stores its value in ``number``. Returns ## the number of the parsed characters or 0 in case of an error. @@ -242,8 +242,8 @@ proc captureBetween*(s: string, first: char, second = '\0', start = 0): string = result = "" discard s.parseUntil(result, if second == '\0': first else: second, i) -template integerOutOfRangeError(): ref ValueError = - newException(ValueError, "Parsed integer outside of valid range") +proc integerOutOfRangeError() {.noinline.} = + raise newException(ValueError, "Parsed integer outside of valid range") proc rawParseInt(s: string, b: var BiggestInt, start = 0): int = var @@ -261,13 +261,14 @@ proc rawParseInt(s: string, b: var BiggestInt, start = 0): int = if b >= (low(int) + c) div 10: b = b * 10 - c else: - raise integerOutOfRangeError() + integerOutOfRangeError() inc(i) while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored if sign == -1 and b == low(int): - raise integerOutOfRangeError() - b = b * sign - result = i - start + integerOutOfRangeError() + else: + b = b * sign + result = i - start proc parseBiggestInt*(s: string, number: var BiggestInt, start = 0): int {. rtl, extern: "npuParseBiggestInt", noSideEffect, raises: [ValueError].} = @@ -290,7 +291,7 @@ proc parseInt*(s: string, number: var int, start = 0): int {. result = parseBiggestInt(s, res, start) when sizeof(int) <= 4: if res < low(int) or res > high(int): - raise integerOutOfRangeError() + integerOutOfRangeError() if result != 0: number = int(res) @@ -326,7 +327,7 @@ proc rawParseUInt(s: string, b: var BiggestUInt, start = 0): int = prev = 0.BiggestUInt i = start if i < s.len - 1 and s[i] == '-' and s[i + 1] in {'0'..'9'}: - raise integerOutOfRangeError() + integerOutOfRangeError() if i < s.len and s[i] == '+': inc(i) # Allow if i < s.len and s[i] in {'0'..'9'}: b = 0 @@ -334,7 +335,7 @@ proc rawParseUInt(s: string, b: var BiggestUInt, start = 0): int = prev = res res = res * 10 + (ord(s[i]) - ord('0')).BiggestUInt if prev > res: - raise integerOutOfRangeError() + integerOutOfRangeError() inc(i) while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored b = res @@ -361,7 +362,7 @@ proc parseUInt*(s: string, number: var uint, start = 0): int {. result = parseBiggestUInt(s, res, start) when sizeof(BiggestUInt) > sizeof(uint) and sizeof(uint) <= 4: if res > 0xFFFF_FFFF'u64: - raise integerOutOfRangeError() + integerOutOfRangeError() if result != 0: number = uint(res) -- cgit 1.4.1-2-gfad0 From fc30cf02bca70341846d07e02b9af0841ba9f985 Mon Sep 17 00:00:00 2001 From: alaviss Date: Thu, 17 Jan 2019 03:06:32 +0700 Subject: nimsuggest: add an option to bind to a free port (#10328) --- nimsuggest/nimsuggest.nim | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim index c24a8bafa..1b5e8326d 100644 --- a/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -35,6 +35,7 @@ Usage: nimsuggest [options] projectfile.nim Options: + --autobind automatically binds into a free port --port:PORT port, by default 6000 --address:HOST binds to that address, by default "" --stdin read commands from stdin and write results to @@ -50,6 +51,8 @@ Options: The server then listens to the connection and takes line-based commands. +If --autobind is used, the binded port number will be printed to stdout. + In addition, all command line options of Nim that do not affect code generation are supported. """ @@ -68,6 +71,7 @@ var gEmitEof: bool # whether we write '!EOF!' dummy lines gLogging = defined(logging) gRefresh: bool + gAutoBind = false requests: Channel[string] results: Channel[Suggest] @@ -306,9 +310,15 @@ proc replCmdline(x: ThreadParams) {.thread.} = proc replTcp(x: ThreadParams) {.thread.} = var server = newSocket() - server.bindAddr(x.port, x.address) + if gAutoBind: + let port = server.connectToNextFreePort(x.address) + server.listen() + echo port + stdout.flushFile() + else: + server.bindAddr(x.port, x.address) + server.listen() var inp = "".TaintedString - server.listen() while true: var stdoutSocket = newSocket() accept(server, stdoutSocket) @@ -544,6 +554,9 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string; conf: ConfigRef) = of "help", "h": stdout.writeline(Usage) quit() + of "autobind": + gMode = mtcp + gAutoBind = true of "port": gPort = parseInt(p.val).Port gMode = mtcp -- cgit 1.4.1-2-gfad0 From 5df411bd6e51e801dd06fb6b8b6f1804f8dcf9b1 Mon Sep 17 00:00:00 2001 From: Oscar Nihlgård Date: Wed, 16 Jan 2019 21:08:32 +0100 Subject: Revert export of times.CTime; add std/time_t instead. (#10319) * Revert export of times.CTime * Add std/time_t --- lib/pure/times.nim | 11 ++++------- lib/std/time_t.nim | 23 +++++++++++++++++++++++ tools/kochdocs.nim | 1 + 3 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 lib/std/time_t.nim diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 166dfd446..0104a97c1 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -217,7 +217,7 @@ when defined(JS): elif defined(posix): import posix - type CTime* = posix.Time + type CTime = posix.Time var realTimeClockId {.importc: "CLOCK_REALTIME", header: "".}: Clockid @@ -233,13 +233,10 @@ elif defined(posix): tzset() elif defined(windows): - import winlean + import winlean, std/time_t + + type CTime = time_t.Time - when defined(i386) and defined(gcc): - type CTime* {.importc: "time_t", header: "".} = distinct int32 - else: - # newest version of Visual C++ defines time_t to be of 64 bits - type CTime* {.importc: "time_t", header: "".} = distinct int64 # visual c's c runtime exposes these under a different name var timezone {.importc: "_timezone", header: "".}: int diff --git a/lib/std/time_t.nim b/lib/std/time_t.nim new file mode 100644 index 000000000..37918ee6c --- /dev/null +++ b/lib/std/time_t.nim @@ -0,0 +1,23 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2019 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +when defined(nimdoc): + type + impl = distinct int64 + Time* = impl ## \ + ## Wrapper for ``time_t``. On posix, this is an alias to ``posix.Time``. +elif defined(windows): + when defined(i386) and defined(gcc): + type Time* {.importc: "time_t", header: "".} = distinct int32 + else: + # newest version of Visual C++ defines time_t to be of 64 bits + type Time* {.importc: "time_t", header: "".} = distinct int64 +elif defined(posix): + import posix + export posix.Time \ No newline at end of file diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim index 68662bab2..f75994f51 100644 --- a/tools/kochdocs.nim +++ b/tools/kochdocs.nim @@ -219,6 +219,7 @@ lib/pure/collections/heapqueue.nim lib/pure/fenv.nim lib/std/sha1.nim lib/std/varints.nim +lib/std/time_t.nim lib/impure/rdstdin.nim lib/wrappers/linenoise/linenoise.nim lib/pure/strformat.nim -- cgit 1.4.1-2-gfad0 From 8947779dd033966202be58f105dd307c44f143c8 Mon Sep 17 00:00:00 2001 From: Araq Date: Wed, 16 Jan 2019 21:11:21 +0100 Subject: disable one more test for C++ --- tests/objects/tobjcov.nim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/objects/tobjcov.nim b/tests/objects/tobjcov.nim index 817c1fcda..6c587e04d 100644 --- a/tests/objects/tobjcov.nim +++ b/tests/objects/tobjcov.nim @@ -1,8 +1,12 @@ discard """ action: compile +target: "c" """ # Covariance is not type safe: +# Note: `nim cpp` makes it a compile error (after codegen), even with: +# `var f = cast[proc (x: var TA) {.nimcall.}](cast[pointer](bp))`, which +# currently removes all the `cast` in cgen'd code, hence the compile error. type TA = object of RootObj -- cgit 1.4.1-2-gfad0 From 2039dad2736909a9d2091b137eeab4293b508e12 Mon Sep 17 00:00:00 2001 From: Araq Date: Wed, 16 Jan 2019 21:19:38 +0100 Subject: koch.nim: Make bootstrapping in C++ mode robust --- koch.nim | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/koch.nim b/koch.nim index 1d636914d..be8f75986 100644 --- a/koch.nim +++ b/koch.nim @@ -277,14 +277,16 @@ proc boot(args: string) = var output = "compiler" / "nim".exe var finalDest = "bin" / "nim".exe # default to use the 'c' command: - let defaultCommand = if getEnv("NIM_COMPILE_TO_CPP", "false") == "true": "cpp" else: "c" - let bootOptions = if args.len == 0 or args.startsWith("-"): defaultCommand else: "" - echo "boot: defaultCommand: ", defaultCommand, " bootOptions: ", bootOptions + let useCpp = getEnv("NIM_COMPILE_TO_CPP", "false") == "true" let smartNimcache = (if "release" in args: "nimcache/r_" else: "nimcache/d_") & hostOs & "_" & hostCpu copyExe(findStartNim(), 0.thVersion) - for i in 0..2: + for i in 0..2+ord(useCpp): + # do the first iteration in C mode in order to avoid problem #10315: + let defaultCommand = if useCpp and i > 0: "cpp" else: "c" + let bootOptions = if args.len == 0 or args.startsWith("-"): defaultCommand else: "" + echo "iteration: ", i+1 let extraOption = if i == 0: "--skipUserCfg --skipParentCfg" -- cgit 1.4.1-2-gfad0 From 52a54f5f042f2849b50c244332e3169fdc03e194 Mon Sep 17 00:00:00 2001 From: narimiran Date: Thu, 17 Jan 2019 07:15:40 +0100 Subject: documentation: remove author field [ci skip] --- lib/pure/collections/sequtils.nim | 2 -- lib/pure/collections/tables.nim | 2 -- lib/pure/math.nim | 3 --- lib/pure/strutils.nim | 2 -- 4 files changed, 9 deletions(-) diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index f73d37f47..b0d50bce2 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -7,8 +7,6 @@ # distribution, for details about the copyright. # -## :Author: Alexander Mitchell-Robinson (Amrykid) and Nim contributors -## ## Although this module has ``seq`` in its name, it implements operations ## not only for `seq`:idx: type, but for three built-in container types under ## the ``openArray`` umbrella: diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 659b4473b..84ec422d4 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -7,8 +7,6 @@ # distribution, for details about the copyright. # -## :Author: Nim contributors -## ## The ``tables`` module implements variants of an efficient `hash table`:idx: ## (also often named `dictionary`:idx: in other programming languages) that is ## a mapping from keys to values. diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 97d1e7987..460be1cd0 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -7,8 +7,6 @@ # distribution, for details about the copyright. # -## :Author: Nim contributors -## ## *Constructive mathematics is naturally typed.* -- Simon Thompson ## ## Basic math routines for Nim. @@ -237,7 +235,6 @@ proc cumsummed*[T](x: openArray[T]): seq[T] = ## See also: ## * `sum proc <#sum,openArray[T]>`_ ## * `cumsum proc <#cumsum,openArray[T]>`_ for the in-place version - ## * `cumsummed proc <#cumsummed,openArray[T]>`_ runnableExamples: let a = [1, 2, 3, 4] doAssert cumsummed(a) == @[1, 3, 6, 10] diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 4bb640094..8385eb24e 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -7,8 +7,6 @@ # distribution, for details about the copyright. # -## :Author: Nim contributors -## ## The system module defines several common functions for working with strings, ## such as: ## * ``$`` for converting other data-types to strings -- cgit 1.4.1-2-gfad0 From 15584879b91e14565156ca140eef1dc100cf34c4 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 17 Jan 2019 07:55:29 +0100 Subject: Properly wrap discarded statements (#10322) Failing to do so lead the codegen to emit invalid code sometimes, especially when C++ references were involved. Fixes #10241 --- compiler/semexprs.nim | 4 ++-- compiler/semstmts.nim | 24 ++++++++++++++---------- tests/cpp/t10241.nim | 19 +++++++++++++++++++ 3 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 tests/cpp/t10241.nim diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 657df36dd..e74d56a86 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -917,7 +917,7 @@ proc semExprNoType(c: PContext, n: PNode): PNode = let isPush = hintExtendedContext in c.config.notes if isPush: pushInfoContext(c.config, n.info) result = semExpr(c, n, {efWantStmt}) - discardCheck(c, result, {}) + result = discardCheck(c, result, {}) if isPush: popInfoContext(c.config) proc isTypeExpr(n: PNode): bool = @@ -1639,7 +1639,7 @@ proc semProcBody(c: PContext, n: PNode): PNode = a.sons[1] = result result = semAsgn(c, a) else: - discardCheck(c, result, {}) + result = discardCheck(c, result, {}) if c.p.owner.kind notin {skMacro, skTemplate} and c.p.resultSym != nil and c.p.resultSym.typ.isMetaType: diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 3fdbb85db..70a16b290 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -130,13 +130,13 @@ proc fixNilType(c: PContext; n: PNode) = for it in n: fixNilType(c, it) n.typ = nil -proc discardCheck(c: PContext, result: PNode, flags: TExprFlags) = +proc discardCheck(c: PContext, expr: PNode, flags: TExprFlags): PNode = + result = expr if c.matchedConcept != nil or efInTypeof in flags: return if result.typ != nil and result.typ.kind notin {tyStmt, tyVoid}: if implicitlyDiscardable(result): - var n = newNodeI(nkDiscardStmt, result.info, 1) - n[0] = result + result = newNode(nkDiscardStmt, result.info, @[result]) elif result.typ.kind != tyError and c.config.cmd != cmdInteractive: var n = result while n.kind in skipForDiscardable: n = n.lastSon @@ -168,7 +168,8 @@ proc semIf(c: PContext, n: PNode; flags: TExprFlags): PNode = else: illFormedAst(it, c.config) if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or (not hasElse and efInTypeof notin flags): - for it in n: discardCheck(c, it.lastSon, flags) + for it in n: + it.sons[^1] = discardCheck(c, it.sons[^1], flags) result.kind = nkIfStmt # propagate any enforced VoidContext: if typ == c.enforceVoidContext: result.typ = c.enforceVoidContext @@ -266,12 +267,14 @@ proc semTry(c: PContext, n: PNode; flags: TExprFlags): PNode = dec c.p.inTryStmt if isEmptyType(typ) or typ.kind in {tyNil, tyExpr}: - discardCheck(c, n.sons[0], flags) - for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon, flags) + n.sons[0] = discardCheck(c, n.sons[0], flags) + for i in 1..n.len-1: + n.sons[i].sons[^1] = discardCheck(c, n.sons[i].sons[^1], flags) if typ == c.enforceVoidContext: result.typ = c.enforceVoidContext else: - if n.lastSon.kind == nkFinally: discardCheck(c, n.lastSon.lastSon, flags) + if n.lastSon.kind == nkFinally: + n.sons[^1].sons[^1] = discardCheck(c, n.sons[^1].sons[^1], flags) n.sons[0] = fitNode(c, typ, n.sons[0], n.sons[0].info) for i in 1..last: var it = n.sons[i] @@ -679,7 +682,7 @@ proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode = openScope(c) n.sons[length-1] = semExprBranch(c, n.sons[length-1], flags) if efInTypeof notin flags: - discardCheck(c, n.sons[length-1], flags) + n.sons[^1] = discardCheck(c, n.sons[^1], flags) closeScope(c) dec(c.p.nestedLoopCounter) @@ -866,7 +869,8 @@ proc semCase(c: PContext, n: PNode; flags: TExprFlags): PNode = closeScope(c) if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or (not hasElse and efInTypeof notin flags): - for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon, flags) + for i in 1..n.len-1: + n.sons[i].sons[^1] = discardCheck(c, n.sons[i].sons[^1], flags) # propagate any enforced VoidContext: if typ == c.enforceVoidContext: result.typ = c.enforceVoidContext @@ -2029,7 +2033,7 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = n.typ = n.sons[i].typ if not isEmptyType(n.typ): n.kind = nkStmtListExpr elif i != last or voidContext: - discardCheck(c, n.sons[i], flags) + n.sons[i] = discardCheck(c, n.sons[i], flags) else: n.typ = n.sons[i].typ if not isEmptyType(n.typ): n.kind = nkStmtListExpr diff --git a/tests/cpp/t10241.nim b/tests/cpp/t10241.nim new file mode 100644 index 000000000..21d6a0f4e --- /dev/null +++ b/tests/cpp/t10241.nim @@ -0,0 +1,19 @@ +discard """ + targets: "cpp" + action: "compile" +""" + +type + String* {.importcpp: "std::string", header: "string".} = object + +proc initString*(): String + {.importcpp: "std::string()", header: "string".} + +proc append*(this: var String, str: String): var String + {.importcpp: "append", header: "string", discardable.} + +var + s1 = initString() + s2 = initString() + +s1.append s2 -- cgit 1.4.1-2-gfad0 From 42bac52426bf392fcfa3cfeeee716bec3166b709 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 16 Jan 2019 23:00:44 -0800 Subject: [CI] now enables `NIM_COMPILE_TO_CPP=true` to run without allow_failures (#10315) * better fix for `nim cpp` bootstrap error: error: no member named raise_id * [CI] now enables runs NIM_COMPILE_TO_CPP=true without allow_failures * workaround refs #10343 --- .travis.yml | 5 +++-- koch.nim | 21 +++++++++++++-------- lib/system.nim | 7 ++++++- tests/exception/t9657.nim | 2 ++ 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 32a40bcaf..ea5d54ead 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,9 @@ matrix: - os: osx env: NIM_COMPILE_TO_CPP=true - allow_failures: - - env: NIM_COMPILE_TO_CPP=true +# To allow failures for a failing configuration, use something like: +# allow_failures: +# - env: NIM_COMPILE_TO_CPP=true # - os: osx addons: diff --git a/koch.nim b/koch.nim index be8f75986..f70cf2142 100644 --- a/koch.nim +++ b/koch.nim @@ -281,19 +281,24 @@ proc boot(args: string) = let smartNimcache = (if "release" in args: "nimcache/r_" else: "nimcache/d_") & hostOs & "_" & hostCpu - copyExe(findStartNim(), 0.thVersion) - for i in 0..2+ord(useCpp): - # do the first iteration in C mode in order to avoid problem #10315: - let defaultCommand = if useCpp and i > 0: "cpp" else: "c" + let nimStart = findStartNim() + copyExe(nimStart, 0.thVersion) + for i in 0..2: + let defaultCommand = if useCpp: "cpp" else: "c" let bootOptions = if args.len == 0 or args.startsWith("-"): defaultCommand else: "" - echo "iteration: ", i+1 - let extraOption = if i == 0: - "--skipUserCfg --skipParentCfg" + var extraOption = "" + if i == 0: + extraOption.add " --skipUserCfg --skipParentCfg" # Note(D20190115T162028:here): the configs are skipped for bootstrap # (1st iteration) to prevent newer flags from breaking bootstrap phase. # fixes #10030. - else: "" + let ret = execCmdEx(nimStart & " --version") + doAssert ret.exitCode == 0 + let version = ret.output.splitLines[0] + if version.startsWith "Nim Compiler Version 0.19.0": + extraOption.add " -d:nimBoostrapCsources0_19_0" + # remove this when csources get updated exec i.thVersion & " $# $# $# --nimcache:$# compiler" / "nim.nim" % [bootOptions, extraOption, args, smartNimcache] if sameFileContent(output, i.thVersion): diff --git a/lib/system.nim b/lib/system.nim index fb52ee9eb..b479b920a 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -569,7 +569,12 @@ type trace: string else: trace: seq[StackTraceEntry] - raiseId: uint # set when exception is raised + when defined(nimBoostrapCsources0_19_0): + # see #10315, bootstrap with `nim cpp` from csources gave error: + # error: no member named 'raise_id' in 'Exception' + raise_id: uint # set when exception is raised + else: + raiseId: uint # set when exception is raised up: ref Exception # used for stacking exceptions. Not exported! Defect* = object of Exception ## \ diff --git a/tests/exception/t9657.nim b/tests/exception/t9657.nim index 5d5164f4f..c96a0a597 100644 --- a/tests/exception/t9657.nim +++ b/tests/exception/t9657.nim @@ -1,6 +1,8 @@ discard """ action: run exitcode: 1 + target: "c" """ +# todo: remove `target: "c"` workaround once #10343 is properly fixed close stdmsg writeLine stdmsg, "exception!" -- cgit 1.4.1-2-gfad0 From cceb28b5eb9bae4f0fc364899858018a1bf480ae Mon Sep 17 00:00:00 2001 From: Miran Date: Thu, 17 Jan 2019 20:37:39 +0100 Subject: remove queues (#10347) This module was deprecated and superseded by deques 2 years ago. --- lib/pure/collections/queues.nim | 257 ---------------------------------------- tools/kochdocs.nim | 1 - 2 files changed, 258 deletions(-) delete mode 100644 lib/pure/collections/queues.nim diff --git a/lib/pure/collections/queues.nim b/lib/pure/collections/queues.nim deleted file mode 100644 index 9a1d169fb..000000000 --- a/lib/pure/collections/queues.nim +++ /dev/null @@ -1,257 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## Implementation of a `queue`:idx:. The underlying implementation uses a ``seq``. -## -## None of the procs that get an individual value from the queue can be used -## on an empty queue. -## If compiled with `boundChecks` option, those procs will raise an `IndexError` -## on such access. This should not be relied upon, as `-d:release` will -## disable those checks and may return garbage or crash the program. -## -## As such, a check to see if the queue is empty is needed before any -## access, unless your program logic guarantees it indirectly. -## -## .. code-block:: Nim -## proc foo(a, b: Positive) = # assume random positive values for `a` and `b` -## var q = initQueue[int]() # initializes the object -## for i in 1 ..< a: q.add i # populates the queue -## -## if b < q.len: # checking before indexed access -## echo "The element at index position ", b, " is ", q[b] -## -## # The following two lines don't need any checking on access due to the -## # logic of the program, but that would not be the case if `a` could be 0. -## assert q.front == 1 -## assert q.back == a -## -## while q.len > 0: # checking if the queue is empty -## echo q.pop() -## -## Note: For inter thread communication use -## a `Channel `_ instead. - -import math - -{.warning: "`queues` module is deprecated - use `deques` instead".} - -type - Queue* {.deprecated.} [T] = object ## A queue. - data: seq[T] - rd, wr, count, mask: int - -proc initQueue*[T](initialSize: int = 4): Queue[T] = - ## Create a new queue. - ## Optionally, the initial capacity can be reserved via `initialSize` as a - ## performance optimization. The length of a newly created queue will still - ## be 0. - ## - ## `initialSize` needs to be a power of two. If you need to accept runtime - ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math `_ module. - assert isPowerOfTwo(initialSize) - result.mask = initialSize-1 - newSeq(result.data, initialSize) - -proc len*[T](q: Queue[T]): int {.inline.}= - ## Return the number of elements of `q`. - result = q.count - -template emptyCheck(q) = - # Bounds check for the regular queue access. - when compileOption("boundChecks"): - if unlikely(q.count < 1): - raise newException(IndexError, "Empty queue.") - -template xBoundsCheck(q, i) = - # Bounds check for the array like accesses. - when compileOption("boundChecks"): # d:release should disable this. - if unlikely(i >= q.count): # x < q.low is taken care by the Natural parameter - raise newException(IndexError, - "Out of bounds: " & $i & " > " & $(q.count - 1)) - -proc front*[T](q: Queue[T]): T {.inline.}= - ## Return the oldest element of `q`. Equivalent to `q.pop()` but does not - ## remove it from the queue. - emptyCheck(q) - result = q.data[q.rd] - -proc back*[T](q: Queue[T]): T {.inline.} = - ## Return the newest element of `q` but does not remove it from the queue. - emptyCheck(q) - result = q.data[q.wr - 1 and q.mask] - -proc `[]`*[T](q: Queue[T], i: Natural) : T {.inline.} = - ## Access the i-th element of `q` by order of insertion. - ## q[0] is the oldest (the next one q.pop() will extract), - ## q[^1] is the newest (last one added to the queue). - xBoundsCheck(q, i) - return q.data[q.rd + i and q.mask] - -proc `[]`*[T](q: var Queue[T], i: Natural): var T {.inline.} = - ## Access the i-th element of `q` and returns a mutable - ## reference to it. - xBoundsCheck(q, i) - return q.data[q.rd + i and q.mask] - -proc `[]=`* [T] (q: var Queue[T], i: Natural, val : T) {.inline.} = - ## Change the i-th element of `q`. - xBoundsCheck(q, i) - q.data[q.rd + i and q.mask] = val - -iterator items*[T](q: Queue[T]): T = - ## Yield every element of `q`. - var i = q.rd - for c in 0 ..< q.count: - yield q.data[i] - i = (i + 1) and q.mask - -iterator mitems*[T](q: var Queue[T]): var T = - ## Yield every element of `q`. - var i = q.rd - for c in 0 ..< q.count: - yield q.data[i] - i = (i + 1) and q.mask - -iterator pairs*[T](q: Queue[T]): tuple[key: int, val: T] = - ## Yield every (position, value) of `q`. - var i = q.rd - for c in 0 ..< q.count: - yield (c, q.data[i]) - i = (i + 1) and q.mask - -proc contains*[T](q: Queue[T], item: T): bool {.inline.} = - ## Return true if `item` is in `q` or false if not found. Usually used - ## via the ``in`` operator. It is the equivalent of ``q.find(item) >= 0``. - ## - ## .. code-block:: Nim - ## if x in q: - ## assert q.contains x - for e in q: - if e == item: return true - return false - -proc add*[T](q: var Queue[T], item: T) = - ## Add an `item` to the end of the queue `q`. - var cap = q.mask+1 - if unlikely(q.count >= cap): - var n = newSeq[T](cap*2) - for i, x in pairs(q): # don't use copyMem because the GC and because it's slower. - shallowCopy(n[i], x) - shallowCopy(q.data, n) - q.mask = cap*2 - 1 - q.wr = q.count - q.rd = 0 - inc q.count - q.data[q.wr] = item - q.wr = (q.wr + 1) and q.mask - -template default[T](t: typedesc[T]): T = - var v: T - v - -proc pop*[T](q: var Queue[T]): T {.inline, discardable.} = - ## Remove and returns the first (oldest) element of the queue `q`. - emptyCheck(q) - dec q.count - result = q.data[q.rd] - q.data[q.rd] = default(type(result)) - q.rd = (q.rd + 1) and q.mask - -proc enqueue*[T](q: var Queue[T], item: T) = - ## Alias for the ``add`` operation. - q.add(item) - -proc dequeue*[T](q: var Queue[T]): T = - ## Alias for the ``pop`` operation. - q.pop() - -proc `$`*[T](q: Queue[T]): string = - ## Turn a queue into its string representation. - result = "[" - for x in items(q): # Don't remove the items here for reasons that don't fit in this margin. - if result.len > 1: result.add(", ") - result.add($x) - result.add("]") - -when isMainModule: - var q = initQueue[int](1) - q.add(123) - q.add(9) - q.enqueue(4) - var first = q.dequeue() - q.add(56) - q.add(6) - var second = q.pop() - q.add(789) - - assert first == 123 - assert second == 9 - assert($q == "[4, 56, 6, 789]") - - assert q[0] == q.front and q.front == 4 - q[0] = 42 - q[q.len - 1] = 7 - - assert 6 in q and 789 notin q - assert q.find(6) >= 0 - assert q.find(789) < 0 - - for i in -2 .. 10: - if i in q: - assert q.contains(i) and q.find(i) >= 0 - else: - assert(not q.contains(i) and q.find(i) < 0) - - when compileOption("boundChecks"): - try: - echo q[99] - assert false - except IndexError: - discard - - try: - assert q.len == 4 - for i in 0 ..< 5: q.pop() - assert false - except IndexError: - discard - - # grabs some types of resize error. - q = initQueue[int]() - for i in 1 .. 4: q.add i - q.pop() - q.pop() - for i in 5 .. 8: q.add i - assert $q == "[3, 4, 5, 6, 7, 8]" - - # Similar to proc from the documentation example - proc foo(a, b: Positive) = # assume random positive values for `a` and `b`. - var q = initQueue[int]() - assert q.len == 0 - for i in 1 .. a: q.add i - - if b < q.len: # checking before indexed access. - assert q[b] == b + 1 - - # The following two lines don't need any checking on access due to the logic - # of the program, but that would not be the case if `a` could be 0. - assert q.front == 1 - assert q.back == a - - while q.len > 0: # checking if the queue is empty - assert q.pop() > 0 - - #foo(0,0) - foo(8,5) - foo(10,9) - foo(1,1) - foo(2,1) - foo(1,5) - foo(3,2) diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim index f75994f51..376f33ae5 100644 --- a/tools/kochdocs.nim +++ b/tools/kochdocs.nim @@ -177,7 +177,6 @@ lib/pure/collections/lists.nim lib/pure/collections/sharedlist.nim lib/pure/collections/sharedtables.nim lib/pure/collections/intsets.nim -lib/pure/collections/queues.nim lib/pure/collections/deques.nim lib/pure/encodings.nim lib/pure/collections/sequtils.nim -- cgit 1.4.1-2-gfad0 From 214f48eae9b6a02d5ba68ddf0b1e6b9a26bddacb Mon Sep 17 00:00:00 2001 From: Miran Date: Fri, 18 Jan 2019 07:18:32 +0100 Subject: Remove long deprecated stuff (#10332) --- lib/impure/re.nim | 27 +---- lib/js/dom.nim | 30 +---- lib/pure/cgi.nim | 5 - lib/pure/collections/critbits.nim | 6 - lib/pure/collections/intsets.nim | 6 - lib/pure/collections/sets.nim | 7 -- lib/pure/httpclient.nim | 243 -------------------------------------- lib/pure/nativesockets.nim | 63 ---------- lib/pure/net.nim | 39 +----- lib/pure/osproc.nim | 16 --- lib/pure/parseopt2.nim | 9 -- lib/pure/parseutils.nim | 12 -- lib/pure/uri.nim | 27 ----- tests/async/twinasyncrw.nim | 2 +- tests/manyloc/nake/nakefile.nim | 5 +- 15 files changed, 9 insertions(+), 488 deletions(-) diff --git a/lib/impure/re.nim b/lib/impure/re.nim index dc4ee326f..42be4a3c2 100644 --- a/lib/impure/re.nim +++ b/lib/impure/re.nim @@ -561,31 +561,6 @@ proc escapeRe*(s: string): string = result.add("\\x") result.add(toHex(ord(c), 2)) -const ## common regular expressions - reIdentifier* {.deprecated.} = r"\b[a-zA-Z_]+[a-zA-Z_0-9]*\b" - ## describes an identifier - reNatural* {.deprecated.} = r"\b\d+\b" - ## describes a natural number - reInteger* {.deprecated.} = r"\b[-+]?\d+\b" - ## describes an integer - reHex* {.deprecated.} = r"\b0[xX][0-9a-fA-F]+\b" - ## describes a hexadecimal number - reBinary* {.deprecated.} = r"\b0[bB][01]+\b" - ## describes a binary number (example: 0b11101) - reOctal* {.deprecated.} = r"\b0[oO][0-7]+\b" - ## describes an octal number (example: 0o777) - reFloat* {.deprecated.} = r"\b[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\b" - ## describes a floating point number - reEmail* {.deprecated.} = r"\b[a-zA-Z0-9!#$%&'*+/=?^_`{|}~\-]+(?:\. &" & - r"[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@" & - r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+" & - r"(?:[a-zA-Z]{2}|com|org|net|gov|mil|biz|" & - r"info|mobi|name|aero|jobs|museum)\b" - ## describes a common email address - reURL* {.deprecated.} = r"\b(http(s)?|ftp|gopher|telnet|file|notes|ms-help)" & - r":((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-\=\\\.\&]*\b" - ## describes an URL - when isMainModule: doAssert match("(a b c)", rex"\( .* \)") doAssert match("WHiLe", re("while", {reIgnoreCase})) @@ -595,7 +570,7 @@ when isMainModule: doAssert "ABC".match(rex"\d+ | \w+") {.push warnings:off.} - doAssert matchLen("key", re(reIdentifier)) == 3 + doAssert matchLen("key", re"\b[a-zA-Z_]+[a-zA-Z_0-9]*\b") == 3 {.pop.} var pattern = re"[a-z0-9]+\s*=\s*[a-z0-9]+" diff --git a/lib/js/dom.nim b/lib/js/dom.nim index cf219df3d..668dee822 100644 --- a/lib/js/dom.nim +++ b/lib/js/dom.nim @@ -416,12 +416,12 @@ type BoundingRect* {.importc.} = ref object top*, bottom*, left*, right*, x*, y*, width*, height*: float - PerformanceMemory* {.importc.} = ref object + PerformanceMemory* {.importc.} = ref object jsHeapSizeLimit*: float totalJSHeapSize*: float usedJSHeapSize*: float - PerformanceTiming* {.importc.} = ref object + PerformanceTiming* {.importc.} = ref object connectStart*: float domComplete*: float domContentLoadedEventEnd*: float @@ -459,7 +459,6 @@ proc dispatchEvent*(et: EventTarget, ev: Event) proc alert*(w: Window, msg: cstring) proc back*(w: Window) proc blur*(w: Window) -proc captureEvents*(w: Window, eventMask: int) {.deprecated.} proc clearInterval*(w: Window, interval: ref TInterval) proc clearTimeout*(w: Window, timeout: ref TTimeOut) proc close*(w: Window) @@ -478,7 +477,6 @@ proc open*(w: Window, uri, windowname: cstring, properties: cstring = nil): Window proc print*(w: Window) proc prompt*(w: Window, text, default: cstring): cstring -proc releaseEvents*(w: Window, eventMask: int) {.deprecated.} proc resizeBy*(w: Window, x, y: int) proc resizeTo*(w: Window, x, y: int) proc routeEvent*(w: Window, event: Event) @@ -513,7 +511,6 @@ proc setAttribute*(n: Node, name, value: cstring) proc setAttributeNode*(n: Node, attr: Node) # Document "methods" -proc captureEvents*(d: Document, eventMask: int) {.deprecated.} proc createAttribute*(d: Document, identifier: cstring): Node proc createElement*(d: Document, identifier: cstring): Element proc createTextNode*(d: Document, identifier: cstring): Node @@ -524,7 +521,6 @@ proc getElementsByClassName*(d: Document, name: cstring): seq[Element] proc getSelection*(d: Document): cstring proc handleEvent*(d: Document, event: Event) proc open*(d: Document) -proc releaseEvents*(d: Document, eventMask: int) {.deprecated.} proc routeEvent*(d: Document, event: Event) proc write*(d: Document, text: cstring) proc writeln*(d: Document, text: cstring) @@ -605,25 +601,3 @@ proc parseInt*(s: cstring): int {.importc, nodecl.} proc parseInt*(s: cstring, radix: int):int {.importc, nodecl.} proc newEvent*(name: cstring): Event {.importcpp: "new Event(@)", constructor.} - -type - TEventHandlers* {.deprecated.} = EventTargetObj - TWindow* {.deprecated.} = WindowObj - TFrame* {.deprecated.} = FrameObj - TNode* {.deprecated.} = NodeObj - TDocument* {.deprecated.} = DocumentObj - TElement* {.deprecated.} = ElementObj - TLink* {.deprecated.} = LinkObj - TEmbed* {.deprecated.} = EmbedObj - TAnchor* {.deprecated.} = AnchorObj - TOption* {.deprecated.} = OptionObj - TForm* {.deprecated.} = FormObj - TImage* {.deprecated.} = ImageObj - TNodeType* {.deprecated.} = NodeType - TEvent* {.deprecated.} = EventObj - TLocation* {.deprecated.} = LocationObj - THistory* {.deprecated.} = HistoryObj - TNavigator* {.deprecated.} = NavigatorObj - TStyle* {.deprecated.} = StyleObj - TScreen* {.deprecated.} = ScreenObj - TApplet* {.importc, deprecated.} = object of RootObj diff --git a/lib/pure/cgi.nim b/lib/pure/cgi.nim index 869abc9cc..ec3562c35 100644 --- a/lib/pure/cgi.nim +++ b/lib/pure/cgi.nim @@ -337,11 +337,6 @@ proc setStackTraceStdout*() = ## Makes Nim output stacktraces to stdout, instead of server log. errorMessageWriter = writeErrorMessage -proc setStackTraceNewLine*() {.deprecated.} = - ## Makes Nim output stacktraces to stdout, instead of server log. - ## Depracated alias for setStackTraceStdout. - setStackTraceStdout() - proc setCookie*(name, value: string) = ## Sets a cookie. write(stdout, "Set-Cookie: ", name, "=", value, "\n") diff --git a/lib/pure/collections/critbits.nim b/lib/pure/collections/critbits.nim index 1bd13920d..dd91fdb12 100644 --- a/lib/pure/collections/critbits.nim +++ b/lib/pure/collections/critbits.nim @@ -202,12 +202,6 @@ proc `[]`*[T](c: var CritBitTree[T], key: string): var T {.inline, ## If `key` is not in `t`, the ``KeyError`` exception is raised. get(c, key) -proc mget*[T](c: var CritBitTree[T], key: string): var T {.inline, deprecated.} = - ## retrieves the value at ``c[key]``. The value can be modified. - ## If `key` is not in `t`, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. - get(c, key) - iterator leaves[T](n: Node[T]): Node[T] = if n != nil: # XXX actually we could compute the necessary stack size in advance: diff --git a/lib/pure/collections/intsets.nim b/lib/pure/collections/intsets.nim index f6d3a3d11..9d7950ea6 100644 --- a/lib/pure/collections/intsets.nim +++ b/lib/pure/collections/intsets.nim @@ -357,12 +357,6 @@ proc `$`*(s: IntSet): string = ## The `$` operator for int sets. dollarImpl() -proc empty*(s: IntSet): bool {.inline, deprecated.} = - ## Returns true if `s` is empty. This is safe to call even before - ## the set has been initialized with `initIntSet`. Note this never - ## worked reliably and so is deprecated. - result = s.counter == 0 - when isMainModule: import sequtils, algorithm diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index 07fcfe676..d1f941e92 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -191,13 +191,6 @@ proc `[]`*[A](s: var HashSet[A], key: A): var A = else: raise newException(KeyError, "key not found") -proc mget*[A](s: var HashSet[A], key: A): var A {.deprecated.} = - ## returns the element that is actually stored in 's' which has the same - ## value as 'key' or raises the ``KeyError`` exception. This is useful - ## when one overloaded 'hash' and '==' but still needs reference semantics - ## for sharing. Use ``[]`` instead. - s[key] - proc contains*[A](s: HashSet[A], key: A): bool = ## Returns true iff `key` is in `s`. ## diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index b7498b1c5..269ae476f 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -189,12 +189,6 @@ proc body*(response: Response): string = response.body = response.bodyStream.readAll() return response.body -proc `body=`*(response: Response, value: string) {.deprecated.} = - ## Setter for backward compatibility. - ## - ## **This is deprecated and should not be used**. - response.body = value - proc body*(response: AsyncResponse): Future[string] {.async.} = ## Reads the response's body and caches it. The read is performed only ## once. @@ -477,119 +471,6 @@ proc format(p: MultipartData): tuple[contentType, body: string] = result.body.add("--" & bound & "\c\L" & s) result.body.add("--" & bound & "--\c\L") -proc request*(url: string, httpMethod: string, extraHeaders = "", - body = "", sslContext = getDefaultSSL(), timeout = -1, - userAgent = defUserAgent, proxy: Proxy = nil): Response - {.deprecated: "use HttpClient.request instead".} = - ## | Requests ``url`` with the custom method string specified by the - ## | ``httpMethod`` parameter. - ## | Extra headers can be specified and must be separated by ``\c\L`` - ## | An optional timeout can be specified in milliseconds, if reading from the - ## server takes longer than specified an ETimeout exception will be raised. - ## - ## **Deprecated since version 0.15.0**: use ``HttpClient.request`` instead. - var r = if proxy == nil: parseUri(url) else: proxy.url - var hostUrl = if proxy == nil: r else: parseUri(url) - var headers = httpMethod.toUpperAscii() - # TODO: Use generateHeaders further down once it supports proxies. - - var s = newSocket() - defer: s.close() - if s == nil: raiseOSError(osLastError()) - var port = net.Port(80) - if r.scheme == "https": - when defined(ssl): - sslContext.wrapSocket(s) - port = net.Port(443) - else: - raise newException(HttpRequestError, - "SSL support is not available. Cannot connect over SSL. Compile with -d:ssl to enable.") - if r.port != "": - port = net.Port(r.port.parseInt) - - - # get the socket ready. If we are connecting through a proxy to SSL, - # send the appropriate CONNECT header. If not, simply connect to the proper - # host (which may still be the proxy, for normal HTTP) - if proxy != nil and hostUrl.scheme == "https": - when defined(ssl): - var connectHeaders = "CONNECT " - let targetPort = if hostUrl.port == "": 443 else: hostUrl.port.parseInt - connectHeaders.add(hostUrl.hostname) - connectHeaders.add(":" & $targetPort) - connectHeaders.add(" HTTP/1.1\c\L") - connectHeaders.add("Host: " & hostUrl.hostname & ":" & $targetPort & "\c\L") - if proxy.auth != "": - let auth = base64.encode(proxy.auth, newline = "") - connectHeaders.add("Proxy-Authorization: basic " & auth & "\c\L") - connectHeaders.add("\c\L") - if timeout == -1: - s.connect(r.hostname, port) - else: - s.connect(r.hostname, port, timeout) - - s.send(connectHeaders) - let connectResult = parseResponse(s, false, timeout) - if not connectResult.status.startsWith("200"): - raise newException(HttpRequestError, - "The proxy server rejected a CONNECT request, " & - "so a secure connection could not be established.") - sslContext.wrapConnectedSocket(s, handshakeAsClient, hostUrl.hostname) - else: - raise newException(HttpRequestError, "SSL support not available. Cannot " & - "connect via proxy over SSL. Compile with -d:ssl to enable.") - else: - if timeout == -1: - s.connect(r.hostname, port) - else: - s.connect(r.hostname, port, timeout) - - - # now that the socket is ready, prepare the headers - if proxy == nil: - headers.add ' ' - if r.path[0] != '/': headers.add '/' - headers.add(r.path) - if r.query.len > 0: - headers.add("?" & r.query) - else: - headers.add(" " & url) - - headers.add(" HTTP/1.1\c\L") - - if hostUrl.port == "": - add(headers, "Host: " & hostUrl.hostname & "\c\L") - else: - add(headers, "Host: " & hostUrl.hostname & ":" & hostUrl.port & "\c\L") - - if userAgent != "": - add(headers, "User-Agent: " & userAgent & "\c\L") - if proxy != nil and proxy.auth != "": - let auth = base64.encode(proxy.auth, newline = "") - add(headers, "Proxy-Authorization: basic " & auth & "\c\L") - add(headers, extraHeaders) - add(headers, "\c\L") - - # headers are ready. send them, await the result, and close the socket. - s.send(headers) - if body != "": - s.send(body) - - result = parseResponse(s, httpMethod != "HEAD", timeout) - -proc request*(url: string, httpMethod = HttpGET, extraHeaders = "", - body = "", sslContext = getDefaultSSL(), timeout = -1, - userAgent = defUserAgent, proxy: Proxy = nil): Response - {.deprecated.} = - ## | Requests ``url`` with the specified ``httpMethod``. - ## | Extra headers can be specified and must be separated by ``\c\L`` - ## | An optional timeout can be specified in milliseconds, if reading from the - ## server takes longer than specified an ETimeout exception will be raised. - ## - ## **Deprecated since version 0.15.0**: use ``HttpClient.request`` instead. - result = request(url, $httpMethod, extraHeaders, body, sslContext, timeout, - userAgent, proxy) - proc redirection(status: string): bool = const redirectionNRs = ["301", "302", "303", "307"] for i in items(redirectionNRs): @@ -608,130 +489,6 @@ proc getNewLocation(lastURL: string, headers: HttpHeaders): string = parsed.anchor = r.anchor result = $parsed -proc get*(url: string, extraHeaders = "", maxRedirects = 5, - sslContext: SSLContext = getDefaultSSL(), - timeout = -1, userAgent = defUserAgent, - proxy: Proxy = nil): Response {.deprecated.} = - ## | GETs the ``url`` and returns a ``Response`` object - ## | This proc also handles redirection - ## | Extra headers can be specified and must be separated by ``\c\L``. - ## | An optional timeout can be specified in milliseconds, if reading from the - ## server takes longer than specified an ETimeout exception will be raised. - ## - ## **Deprecated since version 0.15.0**: use ``HttpClient.get`` instead. - result = request(url, HttpGET, extraHeaders, "", sslContext, timeout, - userAgent, proxy) - var lastURL = url - for i in 1..maxRedirects: - if result.status.redirection(): - let redirectTo = getNewLocation(lastURL, result.headers) - result = request(redirectTo, HttpGET, extraHeaders, "", sslContext, - timeout, userAgent, proxy) - lastURL = redirectTo - -proc getContent*(url: string, extraHeaders = "", maxRedirects = 5, - sslContext: SSLContext = getDefaultSSL(), - timeout = -1, userAgent = defUserAgent, - proxy: Proxy = nil): string {.deprecated.} = - ## | GETs the body and returns it as a string. - ## | Raises exceptions for the status codes ``4xx`` and ``5xx`` - ## | Extra headers can be specified and must be separated by ``\c\L``. - ## | An optional timeout can be specified in milliseconds, if reading from the - ## server takes longer than specified an ETimeout exception will be raised. - ## - ## **Deprecated since version 0.15.0**: use ``HttpClient.getContent`` instead. - var r = get(url, extraHeaders, maxRedirects, sslContext, timeout, userAgent, - proxy) - if r.status[0] in {'4','5'}: - raise newException(HttpRequestError, r.status) - else: - return r.body - -proc post*(url: string, extraHeaders = "", body = "", - maxRedirects = 5, - sslContext: SSLContext = getDefaultSSL(), - timeout = -1, userAgent = defUserAgent, - proxy: Proxy = nil, - multipart: MultipartData = nil): Response {.deprecated.} = - ## | POSTs ``body`` to the ``url`` and returns a ``Response`` object. - ## | This proc adds the necessary Content-Length header. - ## | This proc also handles redirection. - ## | Extra headers can be specified and must be separated by ``\c\L``. - ## | An optional timeout can be specified in milliseconds, if reading from the - ## server takes longer than specified an ETimeout exception will be raised. - ## | The optional ``multipart`` parameter can be used to create - ## ``multipart/form-data`` POSTs comfortably. - ## - ## **Deprecated since version 0.15.0**: use ``HttpClient.post`` instead. - let (mpContentType, mpBody) = format(multipart) - - template withNewLine(x): untyped = - if x.len > 0 and not x.endsWith("\c\L"): - x & "\c\L" - else: - x - - var xb = mpBody.withNewLine() & body - - var xh = extraHeaders.withNewLine() & - withNewLine("Content-Length: " & $len(xb)) - - if not multipart.isNil: - xh.add(withNewLine("Content-Type: " & mpContentType)) - - result = request(url, HttpPOST, xh, xb, sslContext, timeout, userAgent, - proxy) - var lastURL = url - for i in 1..maxRedirects: - if result.status.redirection(): - let redirectTo = getNewLocation(lastURL, result.headers) - var meth = if result.status != "307": HttpGet else: HttpPost - result = request(redirectTo, meth, xh, xb, sslContext, timeout, - userAgent, proxy) - lastURL = redirectTo - -proc postContent*(url: string, extraHeaders = "", body = "", - maxRedirects = 5, - sslContext: SSLContext = getDefaultSSL(), - timeout = -1, userAgent = defUserAgent, - proxy: Proxy = nil, - multipart: MultipartData = nil): string - {.deprecated.} = - ## | POSTs ``body`` to ``url`` and returns the response's body as a string - ## | Raises exceptions for the status codes ``4xx`` and ``5xx`` - ## | Extra headers can be specified and must be separated by ``\c\L``. - ## | An optional timeout can be specified in milliseconds, if reading from the - ## server takes longer than specified an ETimeout exception will be raised. - ## | The optional ``multipart`` parameter can be used to create - ## ``multipart/form-data`` POSTs comfortably. - ## - ## **Deprecated since version 0.15.0**: use ``HttpClient.postContent`` - ## instead. - var r = post(url, extraHeaders, body, maxRedirects, sslContext, timeout, - userAgent, proxy, multipart) - if r.status[0] in {'4','5'}: - raise newException(HttpRequestError, r.status) - else: - return r.body - -proc downloadFile*(url: string, outputFilename: string, - sslContext: SSLContext = getDefaultSSL(), - timeout = -1, userAgent = defUserAgent, - proxy: Proxy = nil) {.deprecated.} = - ## | Downloads ``url`` and saves it to ``outputFilename`` - ## | An optional timeout can be specified in milliseconds, if reading from the - ## server takes longer than specified an ETimeout exception will be raised. - ## - ## **Deprecated since version 0.16.2**: use ``HttpClient.downloadFile`` - ## instead. - var f: File - if open(f, outputFilename, fmWrite): - f.write(getContent(url, sslContext = sslContext, timeout = timeout, - userAgent = userAgent, proxy = proxy)) - f.close() - else: - fileError("Unable to open file") - proc generateHeaders(requestUrl: Uri, httpMethod: string, headers: HttpHeaders, body: string, proxy: Proxy): string = # GET diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim index 96c377187..6d4df1c5d 100644 --- a/lib/pure/nativesockets.nim +++ b/lib/pure/nativesockets.nim @@ -265,10 +265,6 @@ proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET, else: raiseOSError(osLastError(), $gai_strerror(gaiResult)) -proc dealloc*(ai: ptr AddrInfo) {.deprecated.} = - ## Deprecated since 0.16.2. Use ``freeAddrInfo`` instead. - freeaddrinfo(ai) - proc ntohl*(x: uint32): uint32 = ## Converts 32-bit unsigned integers from network to host byte order. ## On machines where the host byte order is the same as network byte order, @@ -279,15 +275,6 @@ proc ntohl*(x: uint32): uint32 = (x shl 8'u32 and 0xff0000'u32) or (x shl 24'u32) -template ntohl*(x: int32): untyped {.deprecated.} = - ## Converts 32-bit integers from network to host byte order. - ## On machines where the host byte order is the same as network byte order, - ## this is a no-op; otherwise, it performs a 4-byte swap operation. - ## **Warning**: This template is deprecated since 0.14.0, IPv4 - ## addresses are now treated as unsigned integers. Please use the unsigned - ## version of this template. - cast[int32](nativesockets.ntohl(cast[uint32](x))) - proc ntohs*(x: uint16): uint16 = ## Converts 16-bit unsigned integers from network to host byte order. On ## machines where the host byte order is the same as network byte order, @@ -295,39 +282,12 @@ proc ntohs*(x: uint16): uint16 = when cpuEndian == bigEndian: result = x else: result = (x shr 8'u16) or (x shl 8'u16) -template ntohs*(x: int16): untyped {.deprecated.} = - ## Converts 16-bit integers from network to host byte order. On - ## machines where the host byte order is the same as network byte order, - ## this is a no-op; otherwise, it performs a 2-byte swap operation. - ## **Warning**: This template is deprecated since 0.14.0, where port - ## numbers became unsigned integers. Please use the unsigned version of - ## this template. - cast[int16](nativesockets.ntohs(cast[uint16](x))) - -template htonl*(x: int32): untyped {.deprecated.} = - ## Converts 32-bit integers from host to network byte order. On machines - ## where the host byte order is the same as network byte order, this is - ## a no-op; otherwise, it performs a 4-byte swap operation. - ## **Warning**: This template is deprecated since 0.14.0, IPv4 - ## addresses are now treated as unsigned integers. Please use the unsigned - ## version of this template. - nativesockets.ntohl(x) - template htonl*(x: uint32): untyped = ## Converts 32-bit unsigned integers from host to network byte order. On ## machines where the host byte order is the same as network byte order, ## this is a no-op; otherwise, it performs a 4-byte swap operation. nativesockets.ntohl(x) -template htons*(x: int16): untyped {.deprecated.} = - ## Converts 16-bit integers from host to network byte order. - ## On machines where the host byte order is the same as network byte - ## order, this is a no-op; otherwise, it performs a 2-byte swap operation. - ## **Warning**: This template is deprecated since 0.14.0, where port - ## numbers became unsigned integers. Please use the unsigned version of - ## this template. - nativesockets.ntohs(x) - template htons*(x: uint16): untyped = ## Converts 16-bit unsigned integers from host to network byte order. ## On machines where the host byte order is the same as network byte @@ -649,29 +609,6 @@ proc pruneSocketSet(s: var seq[SocketHandle], fd: var TFdSet) = inc(i) setLen(s, L) -proc select*(readfds: var seq[SocketHandle], timeout = 500): int {.deprecated: "use selectRead instead".} = - ## When a socket in ``readfds`` is ready to be read from then a non-zero - ## value will be returned specifying the count of the sockets which can be - ## read from. The sockets which can be read from will also be removed - ## from ``readfds``. - ## - ## ``timeout`` is specified in milliseconds and ``-1`` can be specified for - ## an unlimited time. - ## **Warning:** This is deprecated since version 0.16.2. - ## Use the ``selectRead`` procedure instead. - var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout) - - var rd: TFdSet - var m = 0 - createFdSet((rd), readfds, m) - - if timeout != -1: - result = int(select(cint(m+1), addr(rd), nil, nil, addr(tv))) - else: - result = int(select(cint(m+1), addr(rd), nil, nil, nil)) - - pruneSocketSet(readfds, (rd)) - proc selectRead*(readfds: var seq[SocketHandle], timeout = 500): int = ## When a socket in ``readfds`` is ready to be read from then a non-zero ## value will be returned specifying the count of the sockets which can be diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 840a81f17..d4c5a88b7 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -967,39 +967,6 @@ when defined(posix) or defined(nimdoc): raiseOSError(osLastError()) when defined(ssl): - proc handshake*(socket: Socket): bool - {.tags: [ReadIOEffect, WriteIOEffect], deprecated.} = - ## This proc needs to be called on a socket after it connects. This is - ## only applicable when using ``connectAsync``. - ## This proc performs the SSL handshake. - ## - ## Returns ``False`` whenever the socket is not yet ready for a handshake, - ## ``True`` whenever handshake completed successfully. - ## - ## A SslError error is raised on any other errors. - ## - ## **Note:** This procedure is deprecated since version 0.14.0. - result = true - if socket.isSSL: - var ret = SSLConnect(socket.sslHandle) - if ret <= 0: - var errret = SSLGetError(socket.sslHandle, ret) - case errret - of SSL_ERROR_ZERO_RETURN: - raiseSSLError("TLS/SSL connection failed to initiate, socket closed prematurely.") - of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT, - SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE: - return false - of SSL_ERROR_WANT_X509_LOOKUP: - raiseSSLError("Function for x509 lookup has been called.") - of SSL_ERROR_SYSCALL, SSL_ERROR_SSL: - raiseSSLError() - else: - raiseSSLError("Unknown Error") - socket.sslNoHandshake = false - else: - raiseSSLError("Socket is not an SSL socket.") - proc gotHandshake*(socket: Socket): bool = ## Determines whether a handshake has occurred between a client (``socket``) ## and the server that ``socket`` is connected to. @@ -1026,7 +993,7 @@ proc select(readfd: Socket, timeout = 500): int = return 1 var fds = @[readfd.fd] - result = select(fds, timeout) + result = selectRead(fds, timeout) proc isClosed(socket: Socket): bool = socket.fd == osInvalidSocket @@ -1694,7 +1661,5 @@ proc connect*(socket: Socket, address: string, port = Port(0), when defineSsl and not defined(nimdoc): if socket.isSSL: socket.fd.setBlocking(true) - {.warning[Deprecated]: off.} - doAssert socket.handshake() - {.warning[Deprecated]: on.} + doAssert socket.gotHandshake() socket.fd.setBlocking(true) diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index dfe75d998..e7ab395ae 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -60,9 +60,6 @@ type Process* = ref ProcessObj ## represents an operating system process -const poUseShell* {.deprecated.} = poUsePath - ## Deprecated alias for poUsePath. - proc execProcess*(command: string, workingDir: string = "", args: openArray[string] = [], @@ -335,19 +332,6 @@ proc execProcesses*(cmds: openArray[string], if afterRunEvent != nil: afterRunEvent(i, p) close(p) -proc select*(readfds: var seq[Process], timeout = 500): int - {.benign, deprecated.} - ## `select` with a sensible Nim interface. `timeout` is in milliseconds. - ## Specify -1 for no timeout. Returns the number of processes that are - ## ready to read from. The processes that are ready to be read from are - ## removed from `readfds`. - ## - ## **Warning**: This function may give unexpected or completely wrong - ## results on Windows. - ## - ## **Deprecated since version 0.17.0**: This procedure isn't cross-platform - ## and so should not be used in newly written code. - when not defined(useNimRtl): proc execProcess(command: string, workingDir: string = "", diff --git a/lib/pure/parseopt2.nim b/lib/pure/parseopt2.nim index a84943cf9..9fd6cd2c7 100644 --- a/lib/pure/parseopt2.nim +++ b/lib/pure/parseopt2.nim @@ -55,15 +55,6 @@ proc initOptParser*(cmdline: seq[string]): OptParser {.rtl.} = result.cmd = @cmdline -proc initOptParser*(cmdline: string): OptParser {.rtl, deprecated.} = - ## Initalizes option parses with cmdline. Splits cmdline in on spaces - ## and calls initOptParser(openarray[string]) - ## Do not use. - if cmdline == "": # backward compatibility - return initOptParser(@[]) - else: - return initOptParser(cmdline.split) - when not defined(createNimRtl): proc initOptParser*(): OptParser = ## Initializes option parser from current command line arguments. diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index ee8453b21..bd8592a4c 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -138,18 +138,6 @@ proc parseIdent*(s: string, start = 0): string = while i < s.len and s[i] in IdentChars: inc(i) result = substr(s, start, i-1) -proc parseToken*(s: string, token: var string, validChars: set[char], - start = 0): int {.inline, deprecated.} = - ## parses a token and stores it in ``token``. Returns - ## the number of the parsed characters or 0 in case of an error. A token - ## consists of the characters in `validChars`. - ## - ## **Deprecated since version 0.8.12**: Use ``parseWhile`` instead. - var i = start - while i < s.len and s[i] in validChars: inc(i) - result = i-start - token = substr(s, start, i-1) - proc skipWhitespace*(s: string, start = 0): int {.inline.} = ## skips the whitespace starting at ``s[start]``. Returns the number of ## skipped characters. diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim index d296017dd..3347ed546 100644 --- a/lib/pure/uri.nim +++ b/lib/pure/uri.nim @@ -18,33 +18,6 @@ type hostname*, port*, path*, query*, anchor*: string opaque*: bool -{.push warning[deprecated]: off.} -proc `$`*(url: Url): string {.deprecated: "use Uri instead".} = - ## **Deprecated since 0.9.6**: Use ``Uri`` instead. - return string(url) - -proc `/`*(a, b: Url): Url {.deprecated: "use Uri instead".} = - ## Joins two URLs together, separating them with / if needed. - ## - ## **Deprecated since 0.9.6**: Use ``Uri`` instead. - var urlS = $a - var bS = $b - if urlS == "": return b - if urlS[urlS.len-1] != '/': - urlS.add('/') - if bS[0] == '/': - urlS.add(bS.substr(1)) - else: - urlS.add(bs) - result = Url(urlS) - -proc add*(url: var Url, a: Url) {.deprecated: "use Uri instead".} = - ## Appends url to url. - ## - ## **Deprecated since 0.9.6**: Use ``Uri`` instead. - url = url / a -{.pop.} - proc encodeUrl*(s: string, usePlus=true): string = ## Encodes a URL according to RFC3986. ## diff --git a/tests/async/twinasyncrw.nim b/tests/async/twinasyncrw.nim index 64c5d6c26..6763eb5a2 100644 --- a/tests/async/twinasyncrw.nim +++ b/tests/async/twinasyncrw.nim @@ -46,7 +46,7 @@ when defined(windows): success = false it = it.ai_next - dealloc(aiList) + freeAddrInfo(aiList) if not success: retFuture.fail(newException(OSError, osErrorMsg(lastError))) return retFuture diff --git a/tests/manyloc/nake/nakefile.nim b/tests/manyloc/nake/nakefile.nim index 9c66ad71c..35ed3cbb0 100644 --- a/tests/manyloc/nake/nakefile.nim +++ b/tests/manyloc/nake/nakefile.nim @@ -83,6 +83,7 @@ task "download", "download game assets": var skipAssets = false path = expandFilename("data") + client = newHttpClient() path.add DirSep path.add(extractFilename(GameAssets)) if existsFile(path): @@ -101,7 +102,7 @@ task "download", "download game assets": echo "Downloading from ", GameAssets if not skipAssets: echo "Downloading to ", path - downloadFile GameAssets, path + client.downloadFile(GameAssets, path) echo "Download finished" let targetDir = parentDir(parentDir(path)) @@ -126,7 +127,7 @@ task "download", "download game assets": else: return path = extractFilename(BinLibs) - downloadFile BinLibs, path + client.downloadFile(BinLibs, path) echo "Downloaded dem libs ", path when true: echo "Unpack it yourself, sorry." else: ## this crashes, dunno why -- cgit 1.4.1-2-gfad0 From 1e63f1edb3fa594bcdce39eb5195d5f5d2644e67 Mon Sep 17 00:00:00 2001 From: cooldome Date: Fri, 18 Jan 2019 07:51:22 +0000 Subject: destructors: first step towards fixing #9617 (#10341) --- compiler/semasgn.nim | 126 ++++++++++++++++++------------------- compiler/semdata.nim | 7 +++ compiler/semexprs.nim | 4 +- compiler/semstmts.nim | 17 ++++- tests/destructor/helper.nim | 3 + tests/destructor/terror_module.nim | 20 ++++++ 6 files changed, 110 insertions(+), 67 deletions(-) create mode 100644 tests/destructor/helper.nim create mode 100644 tests/destructor/terror_module.nim diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim index c1ecaf8a2..9f1ef313b 100644 --- a/compiler/semasgn.nim +++ b/compiler/semasgn.nim @@ -14,7 +14,7 @@ type TLiftCtx = object - c: PContext + graph: ModuleGraph info: TLineInfo # for construction kind: TTypeAttachedOp fn: PSym @@ -22,7 +22,7 @@ type recurse: bool proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) -proc liftBody(c: PContext; typ: PType; kind: TTypeAttachedOp; +proc liftBody(g: ModuleGraph; typ: PType; kind: TTypeAttachedOp; info: TLineInfo): PSym {.discardable.} proc at(a, i: PNode, elemType: PType): PNode = @@ -33,7 +33,7 @@ proc at(a, i: PNode, elemType: PType): PNode = proc liftBodyTup(c: var TLiftCtx; t: PType; body, x, y: PNode) = for i in 0 ..< t.len: - let lit = lowerings.newIntLit(c.c.graph, x.info, i) + let lit = lowerings.newIntLit(c.graph, x.info, i) liftBodyAux(c, t.sons[i], body, x.at(lit, t.sons[i]), y.at(lit, t.sons[i])) proc dotField(x: PNode, f: PSym): PNode = @@ -77,22 +77,22 @@ proc liftBodyObj(c: var TLiftCtx; n, body, x, y: PNode) = of nkRecList: for t in items(n): liftBodyObj(c, t, body, x, y) else: - illFormedAstLocal(n, c.c.config) + illFormedAstLocal(n, c.graph.config) -proc genAddr(c: PContext; x: PNode): PNode = +proc genAddr(g: ModuleGraph; x: PNode): PNode = if x.kind == nkHiddenDeref: - checkSonsLen(x, 1, c.config) + checkSonsLen(x, 1, g.config) result = x.sons[0] else: - result = newNodeIT(nkHiddenAddr, x.info, makeVarType(c, x.typ)) + result = newNodeIT(nkHiddenAddr, x.info, makeVarType(x.typ.owner, x.typ)) addSon(result, x) -proc newAsgnCall(c: PContext; op: PSym; x, y: PNode): PNode = +proc newAsgnCall(g: ModuleGraph; op: PSym; x, y: PNode): PNode = #if sfError in op.flags: # localError(c.config, x.info, "usage of '$1' is a user-defined error" % op.name.s) result = newNodeI(nkCall, x.info) result.add newSymNode(op) - result.add genAddr(c, x) + result.add genAddr(g, x) result.add y proc newAsgnStmt(le, ri: PNode): PNode = @@ -105,10 +105,10 @@ proc newOpCall(op: PSym; x: PNode): PNode = result.add(newSymNode(op)) result.add x -proc destructorCall(c: PContext; op: PSym; x: PNode): PNode = +proc destructorCall(g: ModuleGraph; op: PSym; x: PNode): PNode = result = newNodeIT(nkCall, x.info, op.typ.sons[0]) result.add(newSymNode(op)) - result.add genAddr(c, x) + result.add genAddr(g, x) proc newDeepCopyCall(op: PSym; x, y: PNode): PNode = result = newAsgnStmt(x, newOpCall(op, y)) @@ -127,13 +127,13 @@ proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode; else: op = field if op == nil: - op = liftBody(c.c, t, c.kind, c.info) + op = liftBody(c.graph, t, c.kind, c.info) if sfError in op.flags: incl c.fn.flags, sfError else: - markUsed(c.c.config, c.info, op, c.c.graph.usageSym) + markUsed(c.graph.config, c.info, op, c.graph.usageSym) onUse(c.info, op) - body.add newAsgnCall(c.c, op, x, y) + body.add newAsgnCall(c.graph, op, x, y) result = true proc considerOverloadedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool = @@ -141,9 +141,9 @@ proc considerOverloadedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool = of attachedDestructor: let op = t.destructor if op != nil: - markUsed(c.c.config, c.info, op, c.c.graph.usageSym) + markUsed(c.graph.config, c.info, op, c.graph.usageSym) onUse(c.info, op) - body.add destructorCall(c.c, op, x) + body.add destructorCall(c.graph, op, x) result = true of attachedAsgn: result = considerAsgnOrSink(c, t, body, x, y, t.assignment) @@ -152,7 +152,7 @@ proc considerOverloadedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool = of attachedDeepCopy: let op = t.deepCopy if op != nil: - markUsed(c.c.config, c.info, op, c.c.graph.usageSym) + markUsed(c.graph.config, c.info, op, c.graph.usageSym) onUse(c.info, op) body.add newDeepCopyCall(op, x, y) result = true @@ -169,13 +169,13 @@ proc addVar(father, v, value: PNode) = addSon(father, vpart) proc declareCounter(c: var TLiftCtx; body: PNode; first: BiggestInt): PNode = - var temp = newSym(skTemp, getIdent(c.c.cache, lowerings.genPrefix), c.fn, c.info) - temp.typ = getSysType(c.c.graph, body.info, tyInt) + var temp = newSym(skTemp, getIdent(c.graph.cache, lowerings.genPrefix), c.fn, c.info) + temp.typ = getSysType(c.graph, body.info, tyInt) incl(temp.flags, sfFromGeneric) var v = newNodeI(nkVarSection, c.info) result = newSymNode(temp) - v.addVar(result, lowerings.newIntLit(c.c.graph, body.info, first)) + v.addVar(result, lowerings.newIntLit(c.graph, body.info, first)) body.add v proc genBuiltin(g: ModuleGraph; magic: TMagic; name: string; i: PNode): PNode = @@ -185,22 +185,22 @@ proc genBuiltin(g: ModuleGraph; magic: TMagic; name: string; i: PNode): PNode = proc genWhileLoop(c: var TLiftCtx; i, dest: PNode): PNode = result = newNodeI(nkWhileStmt, c.info, 2) - let cmp = genBuiltin(c.c.graph, mLeI, "<=", i) - cmp.add genHigh(c.c.graph, dest) - cmp.typ = getSysType(c.c.graph, c.info, tyBool) + let cmp = genBuiltin(c.graph, mLeI, "<=", i) + cmp.add genHigh(c.graph, dest) + cmp.typ = getSysType(c.graph, c.info, tyBool) result.sons[0] = cmp result.sons[1] = newNodeI(nkStmtList, c.info) proc addIncStmt(c: var TLiftCtx; body, i: PNode) = - let incCall = genBuiltin(c.c.graph, mInc, "inc", i) - incCall.add lowerings.newIntLit(c.c.graph, c.info, 1) + let incCall = genBuiltin(c.graph, mInc, "inc", i) + incCall.add lowerings.newIntLit(c.graph, c.info, 1) body.add incCall -proc newSeqCall(c: PContext; x, y: PNode): PNode = +proc newSeqCall(g: ModuleGraph; x, y: PNode): PNode = # don't call genAddr(c, x) here: - result = genBuiltin(c.graph, mNewSeq, "newSeq", x) - let lenCall = genBuiltin(c.graph, mLengthSeq, "len", y) - lenCall.typ = getSysType(c.graph, x.info, tyInt) + result = genBuiltin(g, mNewSeq, "newSeq", x) + let lenCall = genBuiltin(g, mLengthSeq, "len", y) + lenCall.typ = getSysType(g, x.info, tyInt) result.add lenCall proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) = @@ -211,7 +211,7 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) = defaultOp(c, t, body, x, y) of tyArray: if tfHasAsgn in t.flags: - let i = declareCounter(c, body, firstOrd(c.c.config, t)) + let i = declareCounter(c, body, firstOrd(c.graph.config, t)) let whileLoop = genWhileLoop(c, i, x) let elemType = t.lastSon liftBodyAux(c, elemType, whileLoop.sons[1], x.at(i, elemType), @@ -223,12 +223,12 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) = of tySequence: # note that tfHasAsgn is propagated so we need the check on # 'selectedGC' here to determine if we have the new runtime. - if c.c.config.selectedGC == gcDestructors: + if c.graph.config.selectedGC == gcDestructors: discard considerOverloadedOp(c, t, body, x, y) elif tfHasAsgn in t.flags: if c.kind != attachedDestructor: - body.add newSeqCall(c.c, x, y) - let i = declareCounter(c, body, firstOrd(c.c.config, t)) + body.add newSeqCall(c.graph, x, y) + let i = declareCounter(c, body, firstOrd(c.graph.config, t)) let whileLoop = genWhileLoop(c, i, x) let elemType = t.lastSon liftBodyAux(c, elemType, whileLoop.sons[1], x.at(i, elemType), @@ -258,20 +258,20 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) = # have to go through some indirection; we delegate this to the codegen: let call = newNodeI(nkCall, c.info, 2) call.typ = t - call.sons[0] = newSymNode(createMagic(c.c.graph, "deepCopy", mDeepCopy)) + call.sons[0] = newSymNode(createMagic(c.graph, "deepCopy", mDeepCopy)) call.sons[1] = y body.add newAsgnStmt(x, call) of tyVarargs, tyOpenArray: - localError(c.c.config, c.info, "cannot copy openArray") + localError(c.graph.config, c.info, "cannot copy openArray") of tyFromExpr, tyProxy, tyBuiltInTypeClass, tyUserTypeClass, tyUserTypeClassInst, tyCompositeTypeClass, tyAnd, tyOr, tyNot, tyAnything, tyGenericParam, tyGenericBody, tyNil, tyExpr, tyStmt, tyTypeDesc, tyGenericInvocation, tyForward: - internalError(c.c.config, c.info, "assignment requested for type: " & typeToString(t)) + internalError(c.graph.config, c.info, "assignment requested for type: " & typeToString(t)) of tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyVar, tyLent, tyAlias, tySink: liftBodyAux(c, lastSon(t), body, x, y) - of tyOptAsRef: internalError(c.c.config, "liftBodyAux") + of tyOptAsRef: internalError(c.graph.config, "liftBodyAux") proc newProcType(info: TLineInfo; owner: PSym): PType = result = newType(tyProc, owner) @@ -287,54 +287,54 @@ proc addParam(procType: PType; param: PSym) = addSon(procType.n, newSymNode(param)) rawAddSon(procType, param.typ) -proc liftBodyDistinctType(c: PContext; typ: PType; kind: TTypeAttachedOp; info: TLineInfo): PSym = +proc liftBodyDistinctType(g: ModuleGraph; typ: PType; kind: TTypeAttachedOp; info: TLineInfo): PSym = assert typ.kind == tyDistinct let baseType = typ[0] case kind of attachedAsgn: if baseType.assignment == nil: - discard liftBody(c, baseType, kind, info) + discard liftBody(g, baseType, kind, info) typ.assignment = baseType.assignment result = typ.assignment of attachedSink: if baseType.sink == nil: - discard liftBody(c, baseType, kind, info) + discard liftBody(g, baseType, kind, info) typ.sink = baseType.sink result = typ.sink of attachedDeepCopy: if baseType.deepCopy == nil: - discard liftBody(c, baseType, kind, info) + discard liftBody(g, baseType, kind, info) typ.deepCopy = baseType.deepCopy result = typ.deepCopy of attachedDestructor: if baseType.destructor == nil: - discard liftBody(c, baseType, kind, info) + discard liftBody(g, baseType, kind, info) typ.destructor = baseType.destructor result = typ.destructor -proc liftBody(c: PContext; typ: PType; kind: TTypeAttachedOp; +proc liftBody(g: ModuleGraph; typ: PType; kind: TTypeAttachedOp; info: TLineInfo): PSym = if typ.kind == tyDistinct: - return liftBodyDistinctType(c, typ, kind, info) + return liftBodyDistinctType(g, typ, kind, info) var a: TLiftCtx a.info = info - a.c = c + a.graph = g a.kind = kind let body = newNodeI(nkStmtList, info) let procname = case kind - of attachedAsgn: getIdent(c.cache, "=") - of attachedSink: getIdent(c.cache, "=sink") - of attachedDeepCopy: getIdent(c.cache, "=deepcopy") - of attachedDestructor: getIdent(c.cache, "=destroy") + of attachedAsgn: getIdent(g.cache, "=") + of attachedSink: getIdent(g.cache, "=sink") + of attachedDeepCopy: getIdent(g.cache, "=deepcopy") + of attachedDestructor: getIdent(g.cache, "=destroy") result = newSym(skProc, procname, typ.owner, info) a.fn = result a.asgnForType = typ - let dest = newSym(skParam, getIdent(c.cache, "dest"), result, info) - let src = newSym(skParam, getIdent(c.cache, "src"), result, info) - dest.typ = makeVarType(c, typ) + let dest = newSym(skParam, getIdent(g.cache, "dest"), result, info) + let src = newSym(skParam, getIdent(g.cache, "src"), result, info) + dest.typ = makeVarType(typ.owner, typ) src.typ = typ result.typ = newProcType(info, typ.owner) @@ -345,7 +345,7 @@ proc liftBody(c: PContext; typ: PType; kind: TTypeAttachedOp; liftBodyAux(a, typ, body, newSymNode(dest).newDeref, newSymNode(src)) # recursion is handled explicitly, do not register the type based operation # before 'liftBodyAux': - if c.config.selectedGC == gcDestructors and + if g.config.selectedGC == gcDestructors and typ.kind in {tySequence, tyString} and body.len == 0: discard "do not cache it yet" else: @@ -364,17 +364,17 @@ proc liftBody(c: PContext; typ: PType; kind: TTypeAttachedOp; incl result.flags, sfFromGeneric -proc getAsgnOrLiftBody(c: PContext; typ: PType; info: TLineInfo): PSym = +proc getAsgnOrLiftBody(g: ModuleGraph; typ: PType; info: TLineInfo): PSym = let t = typ.skipTypes({tyGenericInst, tyVar, tyLent, tyAlias, tySink}) result = t.assignment if result.isNil: - result = liftBody(c, t, attachedAsgn, info) + result = liftBody(g, t, attachedAsgn, info) -proc overloadedAsgn(c: PContext; dest, src: PNode): PNode = - let a = getAsgnOrLiftBody(c, dest.typ, dest.info) - result = newAsgnCall(c, a, dest, src) +proc overloadedAsgn(g: ModuleGraph; dest, src: PNode): PNode = + let a = getAsgnOrLiftBody(g, dest.typ, dest.info) + result = newAsgnCall(g, a, dest, src) -proc liftTypeBoundOps*(c: PContext; typ: PType; info: TLineInfo) = +proc liftTypeBoundOps*(g: ModuleGraph; typ: PType; info: TLineInfo) = ## In the semantic pass this is called in strategic places ## to ensure we lift assignment, destructors and moves properly. ## The later 'destroyer' pass depends on it. @@ -386,11 +386,11 @@ proc liftTypeBoundOps*(c: PContext; typ: PType; info: TLineInfo) = let typ = typ.skipTypes({tyGenericInst, tyAlias}) # we generate the destructor first so that other operators can depend on it: if typ.destructor == nil: - liftBody(c, typ, attachedDestructor, info) + liftBody(g, typ, attachedDestructor, info) if typ.assignment == nil: - liftBody(c, typ, attachedAsgn, info) + liftBody(g, typ, attachedAsgn, info) if typ.sink == nil: - liftBody(c, typ, attachedSink, info) + liftBody(g, typ, attachedSink, info) -#proc patchResolvedTypeBoundOp*(c: PContext; n: PNode): PNode = +#proc patchResolvedTypeBoundOp*(g: ModuleGraph; n: PNode): PNode = # if n.kind == nkCall and diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 735c6f6b1..a05bda32d 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -286,6 +286,13 @@ proc makeVarType*(c: PContext, baseType: PType; kind = tyVar): PType = result = newTypeS(kind, c) addSonSkipIntLit(result, baseType) +proc makeVarType*(owner: PSym, baseType: PType; kind = tyVar): PType = + if baseType.kind == kind: + result = baseType + else: + result = newType(kind, owner) + addSonSkipIntLit(result, baseType) + proc makeTypeDesc*(c: PContext, typ: PType): PType = if typ.kind == tyTypeDesc: result = typ diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index e74d56a86..6f2981f63 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -802,7 +802,7 @@ proc afterCallActions(c: PContext; n, orig: PNode, flags: TExprFlags): PNode = result = magicsAfterOverloadResolution(c, result, flags) if result.typ != nil and not (result.typ.kind == tySequence and result.typ.sons[0].kind == tyEmpty): - liftTypeBoundOps(c, result.typ, n.info) + liftTypeBoundOps(c.graph, result.typ, n.info) #result = patchResolvedTypeBoundOp(c, result) if c.matchedConcept == nil: result = evalAtCompileTime(c, result) @@ -1592,7 +1592,7 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = typeMismatch(c.config, n.info, lhs.typ, rhsTyp) n.sons[1] = fitNode(c, le, rhs, goodLineInfo(n[1])) - liftTypeBoundOps(c, lhs.typ, lhs.info) + liftTypeBoundOps(c.graph, lhs.typ, lhs.info) #liftTypeBoundOps(c, n.sons[0].typ, n.sons[0].info) fixAbstractType(c, n) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 70a16b290..de39e95c8 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -476,7 +476,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = # this can only happen for errornous var statements: if typ == nil: continue typeAllowedCheck(c.config, a.info, typ, symkind, if c.matchedConcept != nil: {taConcept} else: {}) - liftTypeBoundOps(c, typ, a.info) + liftTypeBoundOps(c.graph, typ, a.info) var tup = skipTypes(typ, {tyGenericInst, tyAlias, tySink}) if a.kind == nkVarTuple: if tup.kind != tyTuple: @@ -1479,7 +1479,8 @@ proc canonType(c: PContext, t: PType): PType = result = t proc semOverride(c: PContext, s: PSym, n: PNode) = - case s.name.s.normalize + let name = s.name.s.normalize + case name of "=destroy": let t = s.typ var noError = false @@ -1498,6 +1499,9 @@ proc semOverride(c: PContext, s: PSym, n: PNode) = localError(c.config, n.info, errGenerated, "cannot bind another '" & s.name.s & "' to: " & typeToString(obj)) noError = true + if obj.owner.getModule != s.getModule: + localError(c.config, n.info, errGenerated, + "type bound operation `=destroy` can be defined only in the same module with its type (" & obj.typeToString() & ")") if not noError and sfSystemModule notin s.owner.flags: localError(c.config, n.info, errGenerated, "signature for '" & s.name.s & "' must be proc[T: object](x: var T)") @@ -1521,6 +1525,11 @@ proc semOverride(c: PContext, s: PSym, n: PNode) = else: localError(c.config, n.info, errGenerated, "cannot bind 'deepCopy' to: " & typeToString(t)) + + if t.owner.getModule != s.getModule: + localError(c.config, n.info, errGenerated, + "type bound operation `" & name & "` can be defined only in the same module with its type (" & t.typeToString() & ")") + else: localError(c.config, n.info, errGenerated, "signature for 'deepCopy' must be proc[T: ptr|ref](x: T): T") @@ -1551,6 +1560,10 @@ proc semOverride(c: PContext, s: PSym, n: PNode) = else: localError(c.config, n.info, errGenerated, "cannot bind another '" & s.name.s & "' to: " & typeToString(obj)) + if obj.owner.getModule != s.getModule: + localError(c.config, n.info, errGenerated, + "type bound operation `" & name & "` can be defined only in the same module with its type (" & obj.typeToString() & ")") + return if sfSystemModule notin s.owner.flags: localError(c.config, n.info, errGenerated, diff --git a/tests/destructor/helper.nim b/tests/destructor/helper.nim new file mode 100644 index 000000000..466065747 --- /dev/null +++ b/tests/destructor/helper.nim @@ -0,0 +1,3 @@ +type + MyTestObject*[T] = object + p: ptr T diff --git a/tests/destructor/terror_module.nim b/tests/destructor/terror_module.nim new file mode 100644 index 000000000..f3d7c9b26 --- /dev/null +++ b/tests/destructor/terror_module.nim @@ -0,0 +1,20 @@ +discard """ +joinable: false +cmd: "nim check $file" +errormsg: "type bound operation `=deepcopy` can be defined only in the same module with its type (MyTestObject)" +nimout: ''' +terror_module.nim(14, 1) Error: type bound operation `=destroy` can be defined only in the same module with its type (MyTestObject) +terror_module.nim(16, 1) Error: type bound operation `=sink` can be defined only in the same module with its type (MyTestObject) +terror_module.nim(18, 1) Error: type bound operation `=` can be defined only in the same module with its type (MyTestObject) +terror_module.nim(20, 1) Error: type bound operation `=deepcopy` can be defined only in the same module with its type (MyTestObject) +''' +""" +import helper + +proc `=destroy`[T](x: var MyTestObject[T]) = discard + +proc `=sink`[T](x: var MyTestObject[T], y:MyTestObject[T]) = discard + +proc `=`[T](x: var MyTestObject[T], y: MyTestObject[T]) = discard + +proc `=deepcopy`[T](x: ptr MyTestObject[T]): ptr MyTestObject[T] = discard -- cgit 1.4.1-2-gfad0 From 4b4e4fc029d3f151c1e8bcdafc1d9b381a7142a6 Mon Sep 17 00:00:00 2001 From: Tomohiro Date: Fri, 18 Jan 2019 17:00:32 +0900 Subject: Fix unhandled exception that raised when nim was executed with --cc:vcc option (#10356) --- tools/vccexe/vccenv.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/vccexe/vccenv.nim b/tools/vccexe/vccenv.nim index 6ddf2e29a..724fe992a 100644 --- a/tools/vccexe/vccenv.nim +++ b/tools/vccexe/vccenv.nim @@ -44,4 +44,4 @@ proc vccEnvVcVarsAllPath*(version: VccEnvVersion = vsUndefined): string = let key = $version let val = getEnv key if val.len > 0: - result = expandFilename(val & vcvarsallRelativePath) + result = try: expandFilename(val & vcvarsallRelativePath) except OSError: "" -- cgit 1.4.1-2-gfad0 From 27e2ed4375c21b196f5fd403c2199c63dcdb8bf0 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Fri, 18 Jan 2019 00:03:26 -0800 Subject: fix #9629 every binary cmd line option allows on/off/empty=on (#10353) * fix #9629 every binary cmd line option allows on/off/empty=on * workaround refs #10359 --- compiler/commands.nim | 62 +++++++++++++++++------------------------------- doc/advopt.txt | 30 +++++++++++------------ doc/basicopt.txt | 4 ++-- tests/misc/tparseopt.nim | 5 +++- 4 files changed, 43 insertions(+), 58 deletions(-) diff --git a/compiler/commands.nim b/compiler/commands.nim index 47687046f..30521f9ca 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -142,7 +142,7 @@ proc splitSwitch(conf: ConfigRef; switch: string, cmd, arg: var string, pass: TC proc processOnOffSwitch(conf: ConfigRef; op: TOptions, arg: string, pass: TCmdLinePass, info: TLineInfo) = case arg.normalize - of "on": conf.options = conf.options + op + of "","on": conf.options = conf.options + op of "off": conf.options = conf.options - op else: localError(conf, info, errOnOrOffExpectedButXFound % arg) @@ -158,7 +158,7 @@ proc processOnOffSwitchOrList(conf: ConfigRef; op: TOptions, arg: string, pass: proc processOnOffSwitchG(conf: ConfigRef; op: TGlobalOptions, arg: string, pass: TCmdLinePass, info: TLineInfo) = case arg.normalize - of "on": conf.globalOptions = conf.globalOptions + op + of "", "on": conf.globalOptions = conf.globalOptions + op of "off": conf.globalOptions = conf.globalOptions - op else: localError(conf, info, errOnOrOffExpectedButXFound % arg) @@ -414,26 +414,19 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; if pass in {passCmd2, passPP}: addExternalFileToLink(conf, AbsoluteFile arg) of "debuginfo": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optCDebug) + processOnOffSwitchG(conf, {optCDebug}, arg, pass, info) of "embedsrc": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optEmbedOrigSrc) + processOnOffSwitchG(conf, {optEmbedOrigSrc}, arg, pass, info) of "compileonly", "c": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optCompileOnly) + processOnOffSwitchG(conf, {optCompileOnly}, arg, pass, info) of "nolinking": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optNoLinking) + processOnOffSwitchG(conf, {optNoLinking}, arg, pass, info) of "nomain": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optNoMain) + processOnOffSwitchG(conf, {optNoMain}, arg, pass, info) of "forcebuild", "f": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optForceFullMake) + processOnOffSwitchG(conf, {optForceFullMake}, arg, pass, info) of "project": - expectNoArg(conf, switch, arg, pass, info) - incl conf.globalOptions, optWholeProject + processOnOffSwitchG(conf, {optWholeProject}, arg, pass, info) of "gc": expectArg(conf, switch, arg, pass, info) case arg.normalize @@ -499,7 +492,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; else: undefSymbol(conf.symbols, "hotcodereloading") of "oldnewlines": case arg.normalize - of "on": + of "","on": conf.oldNewlines = true defineSymbol(conf.symbols, "nimOldNewlines") of "off": @@ -599,11 +592,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; if pass in {passCmd2, passPP}: conf.implicitIncludes.add findModule(conf, arg, toFullPath(conf, info)).string of "listcmd": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optListCmd) + processOnOffSwitchG(conf, {optListCmd}, arg, pass, info) of "genmapping": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optGenMapping) + processOnOffSwitchG(conf, {optGenMapping}, arg, pass, info) of "os": expectArg(conf, switch, arg, pass, info) if pass in {passCmd1, passPP}: @@ -619,8 +610,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; elif cpu != conf.target.hostCPU: setTarget(conf.target, conf.target.targetOS, cpu) of "run", "r": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optRun) + processOnOffSwitchG(conf, {optRun}, arg, pass, info) of "errormax": expectArg(conf, switch, arg, pass, info) # Note: `nim check` (etc) can overwrite this. @@ -668,21 +658,16 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; of "v2": conf.symbolFiles = v2Sf else: localError(conf, info, "invalid option for --incremental: " & arg) of "skipcfg": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optSkipSystemConfigFile) + processOnOffSwitchG(conf, {optSkipSystemConfigFile}, arg, pass, info) of "skipprojcfg": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optSkipProjConfigFile) + processOnOffSwitchG(conf, {optSkipProjConfigFile}, arg, pass, info) of "skipusercfg": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optSkipUserConfigFile) + processOnOffSwitchG(conf, {optSkipUserConfigFile}, arg, pass, info) of "skipparentcfg": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optSkipParentConfigFiles) + processOnOffSwitchG(conf, {optSkipParentConfigFiles}, arg, pass, info) of "genscript", "gendeps": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optGenScript) - incl(conf.globalOptions, optCompileOnly) + processOnOffSwitchG(conf, {optGenScript}, arg, pass, info) + processOnOffSwitchG(conf, {optCompileOnly}, arg, pass, info) of "colors": processOnOffSwitchG(conf, {optUseColors}, arg, pass, info) of "lib": expectArg(conf, switch, arg, pass, info) @@ -716,16 +701,13 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; expectNoArg(conf, switch, arg, pass, info) conf.ideCmd = ideUse of "stdout": - expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optStdout) + processOnOffSwitchG(conf, {optStdout}, arg, pass, info) of "listfullpaths": - expectNoArg(conf, switch, arg, pass, info) - incl conf.globalOptions, optListFullPaths + processOnOffSwitchG(conf, {optListFullPaths}, arg, pass, info) of "dynliboverride": dynlibOverride(conf, switch, arg, pass, info) of "dynliboverrideall": - expectNoArg(conf, switch, arg, pass, info) - incl conf.globalOptions, optDynlibOverrideAll + processOnOffSwitchG(conf, {optDynlibOverrideAll}, arg, pass, info) of "cs": # only supported for compatibility. Does nothing. expectArg(conf, switch, arg, pass, info) diff --git a/doc/advopt.txt b/doc/advopt.txt index c2e09a3ff..7ab85abfc 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -18,9 +18,9 @@ Advanced commands: Advanced options: -o:FILE, --out:FILE set the output filename - --stdout output to stdout + --stdout:on|off output to stdout --colors:on|off turn compiler messages coloring on|off - --listFullPaths list full paths in messages + --listFullPaths:on|off list full paths in messages -w:on|off|list, --warnings:on|off|list turn all warnings on|off or list all available --warning[X]:on|off turn specific warning X on|off @@ -39,16 +39,16 @@ Advanced options: --nimcache:PATH set the path used for generated files --header:FILE the compiler should produce a .h file (FILE is optional) - -c, --compileOnly compile Nim files only; do not assemble or link - --noLinking compile Nim and generated files but do not link - --noMain do not generate a main procedure - --genScript generate a compile script (in the 'nimcache' + -c, --compileOnly:on|off compile Nim files only; do not assemble or link + --noLinking:on|off compile Nim and generated files but do not link + --noMain:on|off do not generate a main procedure + --genScript:on|off generate a compile script (in the 'nimcache' subdirectory named 'compile_$$project$$scriptext'), implies --compileOnly - --genDeps generate a '.deps' file containing the dependencies + --genDeps:on|off generate a '.deps' file containing the dependencies --os:SYMBOL set the target operating system (cross-compilation) --cpu:SYMBOL set the target processor (cross-compilation) - --debuginfo enables debug information + --debuginfo:on|off enables debug information -t, --passC:OPTION pass an option to the C compiler -l, --passL:OPTION pass an option to the linker --cincludes:DIR modify the C compiler header search path @@ -59,7 +59,7 @@ Advanced options: --docSeeSrcUrl:url activate 'see source' for doc and doc2 commands (see doc.item.seesrc in config/nimdoc.cfg) --lineDir:on|off generation of #line directive on|off - --embedsrc embeds the original source code as comments + --embedsrc:on|off embeds the original source code as comments in the generated output --threadanalysis:on|off turn thread analysis on|off --tlsEmulation:on|off turn thread local storage emulation on|off @@ -77,10 +77,10 @@ Advanced options: --nilseqs:on|off allow 'nil' for strings/seqs for backwards compatibility --oldast:on|off use old AST for backwards compatibility - --skipCfg do not read the nim installation's configuration file - --skipUserCfg do not read the user's configuration file - --skipParentCfg do not read the parent dirs' configuration files - --skipProjCfg do not read the project's configuration file + --skipCfg:on|off do not read the nim installation's configuration file + --skipUserCfg:on|off do not read the user's configuration file + --skipParentCfg:on|off do not read the parent dirs' configuration files + --skipProjCfg:on|off do not read the project's configuration file --gc:refc|markAndSweep|boehm|go|none|regions select the GC to use; default is 'refc' --index:on|off turn index file generation on|off @@ -97,8 +97,8 @@ Advanced options: symbol matching is fuzzy so that --dynlibOverride:lua matches dynlib: "liblua.so.3" - --dynlibOverrideAll makes the dynlib pragma have no effect - --listCmd list the commands used to execute external programs + --dynlibOverrideAll:on|off makes the dynlib pragma have no effect + --listCmd:on|off list the commands used to execute external programs --parallelBuild:0|1|... perform a parallel build value = number of processors (0 for auto-detect) --incremental:on|off only recompile the changed modules (experimental!) diff --git a/doc/basicopt.txt b/doc/basicopt.txt index a9166d36c..96c9ced3d 100644 --- a/doc/basicopt.txt +++ b/doc/basicopt.txt @@ -15,7 +15,7 @@ Options: (Optionally: Define the value for that symbol, see: "compile time define pragmas") -u, --undef:SYMBOL undefine a conditional symbol - -f, --forceBuild force rebuilding of all modules + -f, --forceBuild:on|off force rebuilding of all modules --stackTrace:on|off turn stack tracing on|off --lineTrace:on|off turn line tracing on|off --threads:on|off turn support for multi-threading on|off @@ -35,7 +35,7 @@ Options: --debugger:native|endb use native debugger (gdb) | ENDB (experimental) --app:console|gui|lib|staticlib generate a console app|GUI app|DLL|static library - -r, --run run the compiled program with given arguments + -r, --run:on|off run the compiled program with given arguments --fullhelp show all command line switches -h, --help show this help diff --git a/tests/misc/tparseopt.nim b/tests/misc/tparseopt.nim index d8824e522..7d5071c3e 100644 --- a/tests/misc/tparseopt.nim +++ b/tests/misc/tparseopt.nim @@ -121,7 +121,10 @@ else: block: # fix #9842 let exe = buildDir / "D20190112T145450".addFileExt(ExeExt) - defer: removeFile exe + defer: + when not defined(windows): + # workaround #10359 ; innocuous to skip since we're saving under `buildDir` + removeFile exe let args = @["a1b", "a2 b", "", "a4\"b", "a5'b", r"a6\b", "a7\'b"] let cmd = "$# c -r --verbosity:0 -o:$# -d:testament_tparseopt $# $#" % [getCurrentCompilerExe(), exe, currentSourcePath(), -- cgit 1.4.1-2-gfad0 From f11f36e7d5c2dc9a8f13f84ead360369fb05eaf3 Mon Sep 17 00:00:00 2001 From: Ico Doornekamp Date: Fri, 18 Jan 2019 09:04:12 +0100 Subject: Fixed getCustomPragmaVal to allow multiple fields in custom annotations (#10289) --- lib/core/macros.nim | 15 ++++++++++++--- tests/pragmas/tcustom_pragma.nim | 9 +++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 08ee05152..3a85324ba 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -1500,9 +1500,18 @@ macro getCustomPragmaVal*(n: typed, cp: typed{nkSym}): untyped = let pragmaNode = customPragmaNode(n) for p in pragmaNode: if p.kind in nnkPragmaCallKinds and p.len > 0 and p[0].kind == nnkSym and p[0] == cp: - return p[1] - - error(n.repr & " doesn't have a pragma named " & cp.repr()) # returning an empty node results in most cases in a cryptic error, + if p.len == 2: + result = p[1] + else: + let def = p[0].getImpl[3] + result = newTree(nnkPar) + for i in 1.. Date: Fri, 18 Jan 2019 14:17:25 +0100 Subject: better docs: parseutils --- lib/pure/parseutils.nim | 215 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 180 insertions(+), 35 deletions(-) diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index bd8592a4c..c2d52ebcc 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -7,9 +7,46 @@ # distribution, for details about the copyright. # -## This module contains helpers for parsing tokens, numbers, identifiers, etc. +## This module contains helpers for parsing tokens, numbers, integers, floats, +## identifiers, etc. ## ## To unpack raw bytes look at the `streams `_ module. +## +## +## .. code-block:: +## import parseutils +## +## let logs = @["2019-01-10: OK_", "2019-01-11: FAIL_", "2019-01: aaaa"] +## +## for log in logs: +## var res: string +## if parseUntil(log, res, ':') == 10: # YYYY-MM-DD == 10 +## echo res & " - " & captureBetween(log, ' ', '_') +## # => 2019-01-10 - OK +## +## +## .. code-block:: +## import parseutils +## from strutils import Digits, parseInt +## +## let userInput1 = "2019 school start" +## let userInput2 = "3 years back" +## +## let startYear = input1[0..skipWhile(input1, Digits)-1] # 2019 +## let yearsBack = input2[0..skipWhile(input2, Digits)-1] # 3 +## +## echo "Examination is in " & $(parseInt(startYear) + parseInt(yearsBack)) +## +## +## **See also:** +## * `strutils module`_ for combined and identical parsing proc's +## * `json module`_ for a JSON parser +## * `parsecfg module`_ for a configuration file parser +## * `parsecsv module`_ for a simple CSV (comma separated value) parser +## * `parseopt module`_ for a command line parser +## * `parsexml module`_ for a XML / HTML parser +## * `other parsers`_ for other parsers + {.deadCodeElim: on.} # dce option deprecated @@ -35,21 +72,20 @@ proc parseHex*(s: string, number: var int, start = 0; maxLen = 0): int {. ## proc is sensitive to the already existing value of ``number`` and will ## likely not do what you want unless you make sure ``number`` is zero. You ## can use this feature to *chain* calls, though the result int will quickly - ## overflow. Example: - ## - ## .. code-block:: nim - ## var value = 0 - ## discard parseHex("0x38", value) - ## assert value == 56 - ## discard parseHex("0x34", value) - ## assert value == 56 * 256 + 52 - ## value = -1 - ## discard parseHex("0x38", value) - ## assert value == -200 + ## overflow. ## ## If ``maxLen == 0`` the length of the hexadecimal number has no upper bound. ## Else no more than ``start + maxLen`` characters are parsed, up to the ## length of the string. + runnableExamples: + var value = 0 + discard parseHex("0x38", value) + assert value == 56 + discard parseHex("0x34", value) + assert value == 56 * 256 + 52 + value = -1 + discard parseHex("0x38", value) + assert value == -200 var i = start var foundDigit = false # get last index based on minimum `start + maxLen` or `s.len` @@ -80,6 +116,11 @@ proc parseOct*(s: string, number: var int, start = 0, maxLen = 0): int {. ## If ``maxLen == 0`` the length of the octal number has no upper bound. ## Else no more than ``start + maxLen`` characters are parsed, up to the ## length of the string. + runnableExamples: + var res: int + doAssert parseOct("12", res) == 2 + doAssert res == 10 + doAssert parseOct("9", res) == 0 var i = start var foundDigit = false # get last index based on minimum `start + maxLen` or `s.len` @@ -103,6 +144,10 @@ proc parseBin*(s: string, number: var int, start = 0, maxLen = 0): int {. ## If ``maxLen == 0`` the length of the binary number has no upper bound. ## Else no more than ``start + maxLen`` characters are parsed, up to the ## length of the string. + runnableExamples: + var res: int + doAssert parseBin("010011100110100101101101", res) == 24 + doAssert parseBin("3", res) == 0 var i = start var foundDigit = false # get last index based on minimum `start + maxLen` or `s.len` @@ -119,8 +164,16 @@ proc parseBin*(s: string, number: var int, start = 0, maxLen = 0): int {. if foundDigit: result = i-start proc parseIdent*(s: string, ident: var string, start = 0): int = - ## parses an identifier and stores it in ``ident``. Returns + ## Parses an identifier and stores it in ``ident``. Returns ## the number of the parsed characters or 0 in case of an error. + runnableExamples: + var res: string + doAssert parseIdent("Hello World", res, 0) == 5 + doAssert res == "Hello" + doAssert parseIdent("Hello World", res, 1) == 4 + doAssert res == "ello" + doAssert parseIdent("Hello World", res, 6) == 5 + doAssert res == "World" var i = start if i < s.len and s[i] in IdentStartChars: inc(i) @@ -129,8 +182,13 @@ proc parseIdent*(s: string, ident: var string, start = 0): int = result = i-start proc parseIdent*(s: string, start = 0): string = - ## parses an identifier and returns it or an empty string in + ## Parses an identifier and returns it or an empty string in ## case of an error. + runnableExamples: + doAssert parseIdent("Hello World", 0) == "Hello" + doAssert parseIdent("Hello World", 1) == "ello" + doAssert parseIdent("Hello World", 5) == "" + doAssert parseIdent("Hello World", 6) == "World" result = "" var i = start if i < s.len and s[i] in IdentStartChars: @@ -139,20 +197,34 @@ proc parseIdent*(s: string, start = 0): string = result = substr(s, start, i-1) proc skipWhitespace*(s: string, start = 0): int {.inline.} = - ## skips the whitespace starting at ``s[start]``. Returns the number of + ## Skips the whitespace starting at ``s[start]``. Returns the number of ## skipped characters. + runnableExamples: + doAssert skipWhitespace("Hello World", 0) == 0 + doAssert skipWhitespace(" Hello World", 0) == 1 + doAssert skipWhitespace("Hello World", 5) == 1 + doAssert skipWhitespace("Hello World", 5) == 2 while start+result < s.len and s[start+result] in Whitespace: inc(result) proc skip*(s, token: string, start = 0): int {.inline.} = - ## skips the `token` starting at ``s[start]``. Returns the length of `token` + ## Skips the `token` starting at ``s[start]``. Returns the length of `token` ## or 0 if there was no `token` at ``s[start]``. + runnableExamples: + doAssert skip("2019-01-22", "2019", 0) == 4 + doAssert skip("2019-01-22", "19", 0) == 0 + doAssert skip("2019-01-22", "19", 2) == 2 + doAssert skip("CAPlow", "CAP", 0) == 3 + doAssert skip("CAPlow", "cap", 0) == 0 while start+result < s.len and result < token.len and s[result+start] == token[result]: inc(result) if result != token.len: result = 0 proc skipIgnoreCase*(s, token: string, start = 0): int = - ## same as `skip` but case is ignored for token matching. + ## Same as `skip` but case is ignored for token matching. + runnableExamples: + doAssert skipIgnoreCase("CAPlow", "CAP", 0) == 3 + doAssert skipIgnoreCase("CAPlow", "cap", 0) == 3 while start+result < s.len and result < token.len and toLower(s[result+start]) == toLower(token[result]): inc(result) if result != token.len: result = 0 @@ -161,24 +233,45 @@ proc skipUntil*(s: string, until: set[char], start = 0): int {.inline.} = ## Skips all characters until one char from the set `until` is found ## or the end is reached. ## Returns number of characters skipped. + runnableExamples: + doAssert skipUntil("Hello World", {'W', 'e'}, 0) == 1 + doAssert skipUntil("Hello World", {'W'}, 0) == 6 + doAssert skipUntil("Hello World", {'W', 'd'}, 0) == 6 while start+result < s.len and s[result+start] notin until: inc(result) proc skipUntil*(s: string, until: char, start = 0): int {.inline.} = ## Skips all characters until the char `until` is found ## or the end is reached. ## Returns number of characters skipped. + runnableExamples: + doAssert skipUntil("Hello World", 'o', 0) == 4 + doAssert skipUntil("Hello World", 'o', 4) == 0 + doAssert skipUntil("Hello World", 'W', 0) == 6 + doAssert skipUntil("Hello World", 'w', 0) == 11 while start+result < s.len and s[result+start] != until: inc(result) proc skipWhile*(s: string, toSkip: set[char], start = 0): int {.inline.} = ## Skips all characters while one char from the set `token` is found. ## Returns number of characters skipped. + runnableExamples: + doAssert skipWhile("Hello World", {'H', 'e'}) == 2 + doAssert skipWhile("Hello World", {'e'}) == 0 + doAssert skipWhile("Hello World", {'W', 'o', 'r'}, 6) == 3 while start+result < s.len and s[result+start] in toSkip: inc(result) proc parseUntil*(s: string, token: var string, until: set[char], start = 0): int {.inline.} = - ## parses a token and stores it in ``token``. Returns + ## Parses a token and stores it in ``token``. Returns ## the number of the parsed characters or 0 in case of an error. A token ## consists of the characters notin `until`. + runnableExamples: + var myToken: string + doAssert parseUntil("Hello World", myToken, {'W', 'o', 'r'}) == 4 + doAssert myToken == "Hell" + doAssert parseUntil("Hello World", myToken, {'W', 'r'}) == 6 + doAssert myToken == "Hello " + doAssert parseUntil("Hello World", myToken, {'W', 'r'}, 3) == 3 + doAssert myToken == "lo " var i = start while i < s.len and s[i] notin until: inc(i) result = i-start @@ -186,9 +279,17 @@ proc parseUntil*(s: string, token: var string, until: set[char], proc parseUntil*(s: string, token: var string, until: char, start = 0): int {.inline.} = - ## parses a token and stores it in ``token``. Returns + ## Parses a token and stores it in ``token``. Returns ## the number of the parsed characters or 0 in case of an error. A token ## consists of any character that is not the `until` character. + runnableExamples: + var myToken: string + doAssert parseUntil("Hello World", myToken, 'W') == 6 + doAssert myToken == "Hello " + doAssert parseUntil("Hello World", myToken, 'o') == 4 + doAssert myToken == "Hell" + doAssert parseUntil("Hello World", myToken, 'o', 2) == 2 + doAssert myToken == "ll" var i = start while i < s.len and s[i] != until: inc(i) result = i-start @@ -196,9 +297,15 @@ proc parseUntil*(s: string, token: var string, until: char, proc parseUntil*(s: string, token: var string, until: string, start = 0): int {.inline.} = - ## parses a token and stores it in ``token``. Returns + ## Parses a token and stores it in ``token``. Returns ## the number of the parsed characters or 0 in case of an error. A token ## consists of any character that comes before the `until` token. + runnableExamples: + var myToken: string + doAssert parseUntil("Hello World", myToken, "Wor") == 6 + doAssert myToken == "Hello " + doAssert parseUntil("Hello World", myToken, "Wor", 2) == 4 + doAssert myToken == "llo " if until.len == 0: token.setLen(0) return 0 @@ -215,9 +322,15 @@ proc parseUntil*(s: string, token: var string, until: string, proc parseWhile*(s: string, token: var string, validChars: set[char], start = 0): int {.inline.} = - ## parses a token and stores it in ``token``. Returns + ## Parses a token and stores it in ``token``. Returns ## the number of the parsed characters or 0 in case of an error. A token ## consists of the characters in `validChars`. + runnableExamples: + var myToken: string + doAssert parseWhile("Hello World", myToken, {'W', 'o', 'r'}, 0) == 0 + doAssert myToken.len() == 0 + doAssert parseWhile("Hello World", myToken, {'W', 'o', 'r'}, 6) == 3 + doAssert myToken == "Wor" var i = start while i < s.len and s[i] in validChars: inc(i) result = i-start @@ -226,6 +339,10 @@ proc parseWhile*(s: string, token: var string, validChars: set[char], proc captureBetween*(s: string, first: char, second = '\0', start = 0): string = ## Finds the first occurrence of ``first``, then returns everything from there ## up to ``second`` (if ``second`` is '\0', then ``first`` is used). + runnableExamples: + doAssert captureBetween("Hello World", 'e') == "llo World" + doAssert captureBetween("Hello World", 'e', 'r') == "llo Wo" + doAssert captureBetween("Hello World", 'l', start = 6) == "d" var i = skipUntil(s, first, start)+1+start result = "" discard s.parseUntil(result, if second == '\0': first else: second, i) @@ -260,9 +377,13 @@ proc rawParseInt(s: string, b: var BiggestInt, start = 0): int = proc parseBiggestInt*(s: string, number: var BiggestInt, start = 0): int {. rtl, extern: "npuParseBiggestInt", noSideEffect, raises: [ValueError].} = - ## parses an integer starting at `start` and stores the value into `number`. + ## Parses an integer starting at `start` and stores the value into `number`. ## Result is the number of processed chars or 0 if there is no integer. ## `ValueError` is raised if the parsed integer is out of the valid range. + runnableExamples: + var res: BiggestInt + doAssert parseBiggestInt("9223372036854775807", res, 0) == 19 + doAssert res == 9223372036854775807 var res: BiggestInt # use 'res' for exception safety (don't write to 'number' in case of an # overflow exception): @@ -272,9 +393,15 @@ proc parseBiggestInt*(s: string, number: var BiggestInt, start = 0): int {. proc parseInt*(s: string, number: var int, start = 0): int {. rtl, extern: "npuParseInt", noSideEffect, raises: [ValueError].} = - ## parses an integer starting at `start` and stores the value into `number`. + ## Parses an integer starting at `start` and stores the value into `number`. ## Result is the number of processed chars or 0 if there is no integer. ## `ValueError` is raised if the parsed integer is out of the valid range. + runnableExamples: + var res: int + doAssert parseInt("2019", res, 0) == 4 + doAssert res == 2019 + doAssert parseInt("2019", res, 2) == 2 + doAssert res == 19 var res: BiggestInt result = parseBiggestInt(s, res, start) when sizeof(int) <= 4: @@ -285,16 +412,14 @@ proc parseInt*(s: string, number: var int, start = 0): int {. proc parseSaturatedNatural*(s: string, b: var int, start = 0): int {. raises: [].}= - ## parses a natural number into ``b``. This cannot raise an overflow + ## Parses a natural number into ``b``. This cannot raise an overflow ## error. ``high(int)`` is returned for an overflow. ## The number of processed character is returned. ## This is usually what you really want to use instead of `parseInt`:idx:. - ## Example: - ## - ## .. code-block:: nim - ## var res = 0 - ## discard parseSaturatedNatural("848", res) - ## doAssert res == 848 + runnableExamples: + var res = 0 + discard parseSaturatedNatural("848", res) + doAssert res == 848 var i = start if i < s.len and s[i] == '+': inc(i) if i < s.len and s[i] in {'0'..'9'}: @@ -331,9 +456,15 @@ proc rawParseUInt(s: string, b: var BiggestUInt, start = 0): int = proc parseBiggestUInt*(s: string, number: var BiggestUInt, start = 0): int {. rtl, extern: "npuParseBiggestUInt", noSideEffect, raises: [ValueError].} = - ## parses an unsigned integer starting at `start` and stores the value + ## Parses an unsigned integer starting at `start` and stores the value ## into `number`. ## `ValueError` is raised if the parsed integer is out of the valid range. + runnableExamples: + var res: BiggestUInt + doAssert parseBiggestUInt("12", res, 0) == 2 + doAssert res == 12 + doAssert parseBiggestUInt("1111111111111111111", res, 0) == 19 + doAssert res == 1111111111111111111'u64 var res: BiggestUInt # use 'res' for exception safety (don't write to 'number' in case of an # overflow exception): @@ -343,9 +474,15 @@ proc parseBiggestUInt*(s: string, number: var BiggestUInt, start = 0): int {. proc parseUInt*(s: string, number: var uint, start = 0): int {. rtl, extern: "npuParseUInt", noSideEffect, raises: [ValueError].} = - ## parses an unsigned integer starting at `start` and stores the value + ## Parses an unsigned integer starting at `start` and stores the value ## into `number`. ## `ValueError` is raised if the parsed integer is out of the valid range. + runnableExamples: + var res: uint + doAssert parseUInt("3450", res) == 4 + doAssert res == 3450 + doAssert parseUInt("3450", res, 2) == 2 + doAssert res == 50 var res: BiggestUInt result = parseBiggestUInt(s, res, start) when sizeof(BiggestUInt) > sizeof(uint) and sizeof(uint) <= 4: @@ -356,22 +493,30 @@ proc parseUInt*(s: string, number: var uint, start = 0): int {. proc parseBiggestFloat*(s: string, number: var BiggestFloat, start = 0): int {. magic: "ParseBiggestFloat", importc: "nimParseBiggestFloat", noSideEffect.} - ## parses a float starting at `start` and stores the value into `number`. + ## Parses a float starting at `start` and stores the value into `number`. ## Result is the number of processed chars or 0 if a parsing error ## occurred. proc parseFloat*(s: string, number: var float, start = 0): int {. rtl, extern: "npuParseFloat", noSideEffect.} = - ## parses a float starting at `start` and stores the value into `number`. + ## Parses a float starting at `start` and stores the value into `number`. ## Result is the number of processed chars or 0 if there occurred a parsing ## error. + runnableExamples: + var res: float + doAssert parseFloat("32", res, 0) == 2 + doAssert res == 32.0 + doAssert parseFloat("32.57", res, 0) == 5 + doAssert res == 32.57 + doAssert parseFloat("32.57", res, 3) == 2 + doAssert res == 57.00 var bf: BiggestFloat result = parseBiggestFloat(s, bf, start) if result != 0: number = bf type - InterpolatedKind* = enum ## describes for `interpolatedFragments` + InterpolatedKind* = enum ## Describes for `interpolatedFragments` ## which part of the interpolated string is ## yielded; for example in "str$$$var${expr}" ikStr, ## ``str`` part of the interpolated string -- cgit 1.4.1-2-gfad0 From 8399e0f78ad7ef81183cb6797fa78f8c8116e502 Mon Sep 17 00:00:00 2001 From: Arne Döring Date: Fri, 18 Jan 2019 14:36:38 +0100 Subject: deprecate += and friends for bool and enum (#10336) * fixes #10257 * add version to deprecation --- lib/system.nim | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/system.nim b/lib/system.nim index b479b920a..d4a86e53b 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -3886,15 +3886,21 @@ proc gorgeEx*(command: string, input = "", cache = ""): tuple[output: string, ## Same as `gorge` but also returns the precious exit code. discard -proc `+=`*[T: SomeOrdinal|uint|uint64](x: var T, y: T) {. +proc `+=`*[T: SomeInteger](x: var T, y: T) {. magic: "Inc", noSideEffect.} - ## Increments an ordinal + ## Increments an integer -proc `-=`*[T: SomeOrdinal|uint|uint64](x: var T, y: T) {. +proc `+=`*[T: enum|bool](x: var T, y: T) {. + magic: "Inc", noSideEffect, deprecated: "use `inc` instead".} + +proc `-=`*[T: SomeInteger](x: var T, y: T) {. magic: "Dec", noSideEffect.} ## Decrements an ordinal -proc `*=`*[T: SomeOrdinal|uint|uint64](x: var T, y: T) {. +proc `-=`*[T: enum|bool](x: var T, y: T) {. + magic: "Dec", noSideEffect, deprecated: "0.20.0, use `dec` instead".} + +proc `*=`*[T: SomeInteger](x: var T, y: T) {. inline, noSideEffect.} = ## Binary `*=` operator for ordinals x = x * y -- cgit 1.4.1-2-gfad0 From 3cc39c2eb90fa391c3da732525db0cdb52d7f958 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Fri, 18 Jan 2019 23:16:35 +0530 Subject: Show filename in exception raised by expandFilename on windows (#10365) --- lib/pure/os.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 32fa2885e..181bc5728 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1427,7 +1427,7 @@ proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", for x in walkFiles(result.string): result = x if not existsFile(result) and not existsDir(result): - raise newException(OSError, "file does not exist") + raise newException(OSError, "file '" & result & "' does not exist") else: # according to Posix we don't need to allocate space for result pathname. # But we need to free return value with free(3). -- cgit 1.4.1-2-gfad0 From 86a91c1a4a38a669e9ed5409975936f64ad3e532 Mon Sep 17 00:00:00 2001 From: Oscar Nihlgård Date: Fri, 18 Jan 2019 19:15:29 +0100 Subject: Fix parseutils.parseBiggestInt regression (#10348) --- lib/pure/parseutils.nim | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index c2d52ebcc..ba09347a2 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -350,6 +350,10 @@ proc captureBetween*(s: string, first: char, second = '\0', start = 0): string = proc integerOutOfRangeError() {.noinline.} = raise newException(ValueError, "Parsed integer outside of valid range") +# See #6752 +when defined(js): + {.push overflowChecks: off.} + proc rawParseInt(s: string, b: var BiggestInt, start = 0): int = var sign: BiggestInt = -1 @@ -363,18 +367,21 @@ proc rawParseInt(s: string, b: var BiggestInt, start = 0): int = b = 0 while i < s.len and s[i] in {'0'..'9'}: let c = ord(s[i]) - ord('0') - if b >= (low(int) + c) div 10: + if b >= (low(BiggestInt) + c) div 10: b = b * 10 - c else: integerOutOfRangeError() inc(i) while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored - if sign == -1 and b == low(int): + if sign == -1 and b == low(BiggestInt): integerOutOfRangeError() else: b = b * sign result = i - start +when defined(js): + {.pop.} # overflowChecks: off + proc parseBiggestInt*(s: string, number: var BiggestInt, start = 0): int {. rtl, extern: "npuParseBiggestInt", noSideEffect, raises: [ValueError].} = ## Parses an integer starting at `start` and stores the value into `number`. @@ -632,4 +639,8 @@ when isMainModule: doAssert(parseSaturatedNatural("1_000_000", value) == 9) doAssert value == 1_000_000 + var i64Value: int64 + discard parseBiggestInt("9223372036854775807", i64Value) + doAssert i64Value == 9223372036854775807 + {.pop.} -- cgit 1.4.1-2-gfad0 From f7c0360aba4e8ac2857adb85ff838d94cf30ff53 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Sat, 19 Jan 2019 12:46:49 +0100 Subject: allocators: introduce --define:nimMinHeapPages for tuning mmap calls (omg they are slow on OSX...) --- lib/system/alloc.nim | 6 +++--- lib/system/gc_regions.nim | 29 ++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index b090117a9..e938dc475 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -20,7 +20,7 @@ template track(op, address, size) = # Each chunk starts at an address that is divisible by the page size. const - InitialMemoryRequest = 128 * PageSize # 0.5 MB + nimMinHeapPages {.intdefine.} = 128 # 0.5 MB SmallChunkSize = PageSize MaxFli = 30 MaxLog2Sli = 5 # 32, this cannot be increased without changing 'uint32' @@ -588,8 +588,8 @@ proc getBigChunk(a: var MemRegion, size: int): PBigChunk = sysAssert((size and PageMask) == 0, "getBigChunk: unaligned chunk") result = findSuitableBlock(a, fl, sl) if result == nil: - if size < InitialMemoryRequest: - result = requestOsChunks(a, InitialMemoryRequest) + if size < nimMinHeapPages * PageSize: + result = requestOsChunks(a, nimMinHeapPages * PageSize) splitChunk(a, result, size) else: result = requestOsChunks(a, size) diff --git a/lib/system/gc_regions.nim b/lib/system/gc_regions.nim index 8a1446944..3b908fb08 100644 --- a/lib/system/gc_regions.nim +++ b/lib/system/gc_regions.nim @@ -23,6 +23,21 @@ when defined(nimphpext): proc osDeallocPages(p: pointer, size: int) {.inline.} = efree(p) +elif defined(useMalloc): + proc roundup(x, v: int): int {.inline.} = + result = (x + (v-1)) and not (v-1) + proc emalloc(size: int): pointer {.importc: "malloc", header: "".} + proc efree(mem: pointer) {.importc: "free", header: "".} + + proc osAllocPages(size: int): pointer {.inline.} = + emalloc(size) + + proc osTryAllocPages(size: int): pointer {.inline.} = + emalloc(size) + + proc osDeallocPages(p: pointer, size: int) {.inline.} = + efree(p) + else: include osalloc @@ -108,6 +123,8 @@ template `+!`(p: pointer, s: int): pointer = template `-!`(p: pointer, s: int): pointer = cast[pointer](cast[int](p) -% s) +const nimMinHeapPages {.intdefine.} = 4 + proc allocSlowPath(r: var MemRegion; size: int) = # we need to ensure that the underlying linked list # stays small. Say we want to grab 16GB of RAM with some @@ -116,9 +133,8 @@ proc allocSlowPath(r: var MemRegion; size: int) = # 8MB, 16MB, 32MB, 64MB, 128MB, 512MB, 1GB, 2GB, 4GB, 8GB, # 16GB --> list contains only 20 elements! That's reasonable. if (r.totalSize and 1) == 0: - r.nextChunkSize = - if r.totalSize < 64 * 1024: PageSize*4 - else: r.nextChunkSize*2 + r.nextChunkSize = if r.totalSize < 64 * 1024: PageSize*nimMinHeapPages + else: r.nextChunkSize*2 var s = roundup(size+sizeof(BaseChunk), PageSize) var fresh: Chunk if s > r.nextChunkSize: @@ -242,6 +258,13 @@ proc deallocAll*() = tlRegion.deallocAll() proc deallocOsPages(r: var MemRegion) = r.deallocAll() template withScratchRegion*(body: untyped) = + let obs = obstackPtr() + try: + body + finally: + setObstackPtr(obs) + +when false: var scratch: MemRegion let oldRegion = tlRegion tlRegion = scratch -- cgit 1.4.1-2-gfad0 From 9985706fb812156fa6f805989063293ca71599df Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Sat, 19 Jan 2019 12:47:44 +0100 Subject: GC tests: make them take less time to save CI cycles --- tests/gc/gcleak.nim | 2 +- tests/gc/tlists.nim | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/gc/gcleak.nim b/tests/gc/gcleak.nim index 24ac1036a..0b2e6e14d 100644 --- a/tests/gc/gcleak.nim +++ b/tests/gc/gcleak.nim @@ -12,7 +12,7 @@ type proc makeObj(): TTestObj = result.x = "Hello" -for i in 1 .. 1_000_000: +for i in 1 .. 100_000: when defined(gcMarkAndSweep) or defined(boehmgc): GC_fullcollect() var obj = makeObj() diff --git a/tests/gc/tlists.nim b/tests/gc/tlists.nim index 26b32396c..959cc5f7c 100644 --- a/tests/gc/tlists.nim +++ b/tests/gc/tlists.nim @@ -10,15 +10,13 @@ import lists import strutils proc mkleak() = - # allocate 10 MB via linked lists + # allocate 1 MB via linked lists let numberOfLists = 100 for i in countUp(1, numberOfLists): var leakList = initDoublyLinkedList[string]() - let numberOfLeaks = 50000 + let numberOfLeaks = 5000 for j in countUp(1, numberOfLeaks): - let leakSize = 200 - let leaked = newString(leakSize) - leakList.append(leaked) + leakList.append(newString(200)) proc mkManyLeaks() = for i in 0..0: @@ -29,7 +27,7 @@ proc mkManyLeaks() = # lists and bring the memory usage down to a few MB's. GC_fullCollect() when false: echo getOccupiedMem() - if getOccupiedMem() > 8 * 200 * 50_000 * 2: + if getOccupiedMem() > 8 * 200 * 5000 * 2: echo GC_getStatistics() quit "leaking" echo "Success" -- cgit 1.4.1-2-gfad0 From 2371b4be966c5b6e3682295393a3e1566ac3f06b Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Sat, 19 Jan 2019 12:48:25 +0100 Subject: ported havlak and gcbench benchmarks to work with --gc:regions --- tests/gc/gcbench.nim | 68 ++++++++++++++++++++++++++++------------------------ tests/gc/thavlak.nim | 27 +++++++++++++-------- 2 files changed, 54 insertions(+), 41 deletions(-) diff --git a/tests/gc/gcbench.nim b/tests/gc/gcbench.nim index 782daf793..fc5d6864f 100644 --- a/tests/gc/gcbench.nim +++ b/tests/gc/gcbench.nim @@ -65,55 +65,58 @@ proc newNode(L, r: PNode): PNode = const kStretchTreeDepth = 18 # about 16Mb kLongLivedTreeDepth = 16 # about 4Mb - kArraySize = 500000 # about 4Mb + kArraySize = 500000 # about 4Mb kMinTreeDepth = 4 kMaxTreeDepth = 16 +when not declared(withScratchRegion): + template withScratchRegion(body: untyped) = body + # Nodes used by a tree of a given size -proc TreeSize(i: int): int = return ((1 shl (i + 1)) - 1) +proc treeSize(i: int): int = return ((1 shl (i + 1)) - 1) # Number of iterations to use for a given tree depth -proc NumIters(i: int): int = - return 2 * TreeSize(kStretchTreeDepth) div TreeSize(i) +proc numIters(i: int): int = + return 2 * treeSize(kStretchTreeDepth) div treeSize(i) # Build tree top down, assigning to older objects. -proc Populate(iDepth: int, thisNode: PNode) = +proc populate(iDepth: int, thisNode: PNode) = if iDepth <= 0: return else: new(thisNode.left) new(thisNode.right) - Populate(iDepth-1, thisNode.left) - Populate(iDepth-1, thisNode.right) + populate(iDepth-1, thisNode.left) + populate(iDepth-1, thisNode.right) # Build tree bottom-up -proc MakeTree(iDepth: int): PNode = +proc makeTree(iDepth: int): PNode = if iDepth <= 0: new(result) else: - return newNode(MakeTree(iDepth-1), MakeTree(iDepth-1)) + return newNode(makeTree(iDepth-1), makeTree(iDepth-1)) -proc PrintDiagnostics() = +proc printDiagnostics() = echo("Total memory available: " & $getTotalMem() & " bytes") echo("Free memory: " & $getFreeMem() & " bytes") -proc TimeConstruction(depth: int) = +proc timeConstruction(depth: int) = var root, tempTree: PNode iNumIters: int - iNumIters = NumIters(depth) + iNumIters = numIters(depth) echo("Creating " & $iNumIters & " trees of depth " & $depth) var t = epochTime() for i in 0..iNumIters-1: new(tempTree) - Populate(depth, tempTree) + populate(depth, tempTree) tempTree = nil echo("\tTop down construction took " & $(epochTime() - t) & "msecs") t = epochTime() for i in 0..iNumIters-1: - tempTree = MakeTree(depth) + tempTree = makeTree(depth) tempTree = nil echo("\tBottom up construction took " & $(epochTime() - t) & "msecs") @@ -127,39 +130,42 @@ proc main() = echo("Garbage Collector Test") echo(" Stretching memory with a binary tree of depth " & $kStretchTreeDepth) - PrintDiagnostics() + printDiagnostics() var t = epochTime() # Stretch the memory space quickly - tempTree = MakeTree(kStretchTreeDepth) - tempTree = nil + withScratchRegion: + tempTree = makeTree(kStretchTreeDepth) + tempTree = nil # Create a long lived object echo(" Creating a long-lived binary tree of depth " & $kLongLivedTreeDepth) new(longLivedTree) - Populate(kLongLivedTreeDepth, longLivedTree) + populate(kLongLivedTreeDepth, longLivedTree) # Create long-lived array, filling half of it echo(" Creating a long-lived array of " & $kArraySize & " doubles") - newSeq(myarray, kArraySize) - for i in 0..kArraySize div 2 - 1: - myarray[i] = 1.0 / toFloat(i) + withScratchRegion: + newSeq(myarray, kArraySize) + for i in 0..kArraySize div 2 - 1: + myarray[i] = 1.0 / toFloat(i) - PrintDiagnostics() + printDiagnostics() - var d = kMinTreeDepth - while d <= kMaxTreeDepth: - TimeConstruction(d) - inc(d, 2) + var d = kMinTreeDepth + while d <= kMaxTreeDepth: + withScratchRegion: + timeConstruction(d) + inc(d, 2) - if longLivedTree == nil or myarray[1000] != 1.0/1000.0: - echo("Failed") - # fake reference to LongLivedTree - # and array to keep them from being optimized away + if longLivedTree == nil or myarray[1000] != 1.0/1000.0: + echo("Failed") + # fake reference to LongLivedTree + # and array to keep them from being optimized away var elapsed = epochTime() - t - PrintDiagnostics() + printDiagnostics() echo("Completed in " & $elapsed & "ms. Success!") when defined(GC_setMaxPause): diff --git a/tests/gc/thavlak.nim b/tests/gc/thavlak.nim index 09c07785e..2d8df7c1a 100644 --- a/tests/gc/thavlak.nim +++ b/tests/gc/thavlak.nim @@ -12,9 +12,10 @@ Found 1 loops (including artificial root node) (5)''' # bug #3184 -import tables -import sequtils -import sets +import tables, sequtils, sets, strutils + +when not declared(withScratchRegion): + template withScratchRegion(body: untyped) = body type BasicBlock = object @@ -418,8 +419,9 @@ proc run(self: var LoopTesterApp) = echo "15000 dummy loops" for i in 1..15000: - var h = newHavlakLoopFinder(self.cfg, newLsg()) - var res = h.findLoops + withScratchRegion: + var h = newHavlakLoopFinder(self.cfg, newLsg()) + var res = h.findLoops echo "Constructing CFG..." var n = 2 @@ -446,12 +448,17 @@ proc run(self: var LoopTesterApp) = var sum = 0 for i in 1..5: - write stdout, "." - flushFile(stdout) - var hlf = newHavlakLoopFinder(self.cfg, newLsg()) - sum += hlf.findLoops - #echo getOccupiedMem() + withScratchRegion: + write stdout, "." + flushFile(stdout) + var hlf = newHavlakLoopFinder(self.cfg, newLsg()) + sum += hlf.findLoops + #echo getOccupiedMem() echo "\nFound ", loops, " loops (including artificial root node) (", sum, ")" + when false: + echo("Total memory available: " & formatSize(getTotalMem()) & " bytes") + echo("Free memory: " & formatSize(getFreeMem()) & " bytes") + var l = newLoopTesterApp() l.run -- cgit 1.4.1-2-gfad0 From 15aea78405f7e3ba8777984d9531166b6dfa00ae Mon Sep 17 00:00:00 2001 From: "Thomas T. Jarløv" Date: Sat, 19 Jan 2019 15:21:07 +0100 Subject: better docs: base64 (#10371) --- lib/pure/base64.nim | 84 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 25 deletions(-) diff --git a/lib/pure/base64.nim b/lib/pure/base64.nim index bfb8a1666..427f93926 100644 --- a/lib/pure/base64.nim +++ b/lib/pure/base64.nim @@ -9,37 +9,49 @@ ## This module implements a base64 encoder and decoder. ## +## Base64 is an encoding and decoding technique used to convert binary +## data to an ASCII string format. +## Each Base64 digit represents exactly 6 bits of data. Three 8-bit +## bytes (i.e., a total of 24 bits) can therefore be represented by +## four 6-bit Base64 digits. +## +## +## Basic usage +## =========== +## ## Encoding data ## ------------- ## -## In order to encode some text simply call the ``encode`` procedure: -## -## .. code-block::nim -## import base64 -## let encoded = encode("Hello World") -## echo(encoded) # SGVsbG8gV29ybGQ= +## .. code-block::nim +## import base64 +## let encoded = encode("Hello World") +## assert encoded == "SGVsbG8gV29ybGQ=" ## ## Apart from strings you can also encode lists of integers or characters: ## -## .. code-block::nim -## import base64 -## let encodedInts = encode([1,2,3]) -## echo(encodedInts) # AQID -## let encodedChars = encode(['h','e','y']) -## echo(encodedChars) # aGV5 +## .. code-block::nim +## import base64 +## let encodedInts = encode([1,2,3]) +## assert encodedInts == "AQID" +## let encodedChars = encode(['h','e','y']) +## assert encodedChars == "aGV5" ## -## The ``encode`` procedure takes an ``openarray`` so both arrays and sequences -## can be passed as parameters. ## ## Decoding data ## ------------- ## -## To decode a base64 encoded data string simply call the ``decode`` -## procedure: +## .. code-block::nim +## import base64 +## let decoded = decode("SGVsbG8gV29ybGQ=") +## assert decoded == "Hello World" ## -## .. code-block::nim -## import base64 -## echo(decode("SGVsbG8gV29ybGQ=")) # Hello World +## +## See also +## ======== +## +## * `hashes module`_ for efficient computations of hash values for diverse Nim types +## * `md5 module`_ implements the MD5 checksum algorithm +## * `sha1 module`_ implements a sha1 encoder and decoder const cb64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" @@ -100,18 +112,33 @@ template encodeInternal(s: typed, lineLen: int, newLine: string): untyped = discard proc encode*[T:SomeInteger|char](s: openarray[T], lineLen = 75, newLine="\13\10"): string = - ## encodes `s` into base64 representation. After `lineLen` characters, a - ## `newline` is added. + ## Encodes ``s`` into base64 representation. After ``lineLen`` characters, a + ## ``newline`` is added. ## ## This procedure encodes an openarray (array or sequence) of either integers ## or characters. + ## + ## **See also:** + ## * `encode proc<#encode,string,int,string>`_ for encoding a string + ## * `decode proc<#decode,string>`_ for decoding a string + runnableExamples: + assert encode(['n', 'i', 'm']) == "bmlt" + assert encode(@['n', 'i', 'm']) == "bmlt" + assert encode([1, 2, 3, 4, 5]) == "AQIDBAU=" encodeInternal(s, lineLen, newLine) proc encode*(s: string, lineLen = 75, newLine="\13\10"): string = - ## encodes `s` into base64 representation. After `lineLen` characters, a - ## `newline` is added. + ## Encodes ``s`` into base64 representation. After ``lineLen`` characters, a + ## ``newline`` is added. ## ## This procedure encodes a string. + ## + ## **See also:** + ## * `encode proc<#encode,openArray[T],int,string>`_ for encoding an openarray + ## * `decode proc<#decode,string>`_ for decoding a string + runnableExamples: + assert encode("Hello World") == "SGVsbG8gV29ybGQ=" + assert encode("Hello World", 3, "\n") == "SGVs\nbG8g\nV29ybGQ=" encodeInternal(s, lineLen, newLine) proc decodeByte(b: char): int {.inline.} = @@ -123,8 +150,15 @@ proc decodeByte(b: char): int {.inline.} = else: result = 63 proc decode*(s: string): string = - ## decodes a string in base64 representation back into its original form. - ## Whitespace is skipped. + ## Decodes string ``s`` in base64 representation back into its original form. + ## The initial whitespace is skipped. + ## + ## **See also:** + ## * `encode proc<#encode,openArray[T],int,string>`_ for encoding an openarray + ## * `encode proc<#encode,string,int,string>`_ for encoding a string + runnableExamples: + assert decode("SGVsbG8gV29ybGQ=") == "Hello World" + assert decode(" SGVsbG8gV29ybGQ=") == "Hello World" const Whitespace = {' ', '\t', '\v', '\r', '\l', '\f'} var total = ((len(s) + 3) div 4) * 3 # total is an upper bound, as we will skip arbitrary whitespace: -- cgit 1.4.1-2-gfad0 From 4a04470450ab6e6ca59d427837f4bef0c9aea189 Mon Sep 17 00:00:00 2001 From: "Thomas T. Jarløv" Date: Sat, 19 Jan 2019 15:24:39 +0100 Subject: better docs: sha1 (#10374) --- lib/std/sha1.nim | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/lib/std/sha1.nim b/lib/std/sha1.nim index e7e6697cf..b5660f244 100644 --- a/lib/std/sha1.nim +++ b/lib/std/sha1.nim @@ -7,7 +7,32 @@ # distribution, for details about the copyright. # -## Note: Import ``std/sha1`` to use this module +## **Note:** Import ``std/sha1`` to use this module +## +## SHA-1 (Secure Hash Algorithm 1) is a cryptographic hash function which +## takes an input and produces a 160-bit (20-byte) hash value known as a +## message digest. +## +## .. code-block:: +## import std/sha1 +## +## let accessName = secureHash("John Doe") +## assert $accessName == "AE6E4D1209F17B460503904FAD297B31E9CF6362" +## +## .. code-block:: +## import std/sha1 +## +## let +## a = secureHashFile("myFile.nim") +## b = parseSecureHash("10DFAEBF6BFDBC7939957068E2EFACEC4972933C") +## +## if a == b: +## echo "Files match" +## +## **See also:** +## * `base64 module`_ implements a base64 encoder and decoder +## * `hashes module`_ for efficient computations of hash values for diverse Nim types +## * `md5 module`_ implements the MD5 checksum algorithm import strutils from endians import bigEndian32, bigEndian64 @@ -170,23 +195,61 @@ proc finalize(ctx: var Sha1State): Sha1Digest = # Public API proc secureHash*(str: string): SecureHash = + ## Generates a ``SecureHash`` from a ``str``. + ## + ## **See also:** + ## * `secureHashFile proc <#secureHashFile,string>`_ for generating a ``SecureHash`` from a file + ## * `parseSecureHash proc <#parseSecureHash,string>`_ for converting a string ``hash`` to ``SecureHash`` + runnableExamples: + let hash = secureHash("Hello World") + assert hash == parseSecureHash("0A4D55A8D778E5022FAB701977C5D840BBC486D0") var state = newSha1State() state.update(str) SecureHash(state.finalize()) proc secureHashFile*(filename: string): SecureHash = + ## Generates a ``SecureHash`` from a file. + ## + ## **See also:** + ## * `secureHash proc <#secureHash,string>`_ for generating a ``SecureHash`` from a string + ## * `parseSecureHash proc <#parseSecureHash,string>`_ for converting a string ``hash`` to ``SecureHash`` secureHash(readFile(filename)) proc `$`*(self: SecureHash): string = + ## Returns the string representation of a ``SecureHash``. + ## + ## **See also:** + ## * `secureHash proc <#secureHash,string>`_ for generating a ``SecureHash`` from a string + runnableExamples: + let hash = secureHash("Hello World") + assert $hash == "0A4D55A8D778E5022FAB701977C5D840BBC486D0" result = "" for v in Sha1Digest(self): result.add(toHex(int(v), 2)) proc parseSecureHash*(hash: string): SecureHash = + ## Converts a string ``hash`` to ``SecureHash``. + ## + ## **See also:** + ## * `secureHash proc <#secureHash,string>`_ for generating a ``SecureHash`` from a string + ## * `secureHashFile proc <#secureHashFile,string>`_ for generating a ``SecureHash`` from a file + runnableExamples: + let + hashStr = "0A4D55A8D778E5022FAB701977C5D840BBC486D0" + secureHash = secureHash("Hello World") + assert secureHash == parseSecureHash(hashStr) for i in 0 ..< Sha1DigestSize: Sha1Digest(result)[i] = uint8(parseHexInt(hash[i*2] & hash[i*2 + 1])) proc `==`*(a, b: SecureHash): bool = + ## Checks if two ``SecureHash`` values are identical. + runnableExamples: + let + a = secureHash("Hello World") + b = secureHash("Goodbye World") + c = parseSecureHash("0A4D55A8D778E5022FAB701977C5D840BBC486D0") + assert a != b + assert a == c # Not a constant-time comparison, but that's acceptable in this context Sha1Digest(a) == Sha1Digest(b) -- cgit 1.4.1-2-gfad0 From bfeade97917da667f34704de205633dcfe911f2b Mon Sep 17 00:00:00 2001 From: "Thomas T. Jarløv" Date: Sat, 19 Jan 2019 15:31:14 +0100 Subject: better docs: uri (#10373) --- lib/pure/uri.nim | 147 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 112 insertions(+), 35 deletions(-) diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim index 3347ed546..bfb411fc8 100644 --- a/lib/pure/uri.nim +++ b/lib/pure/uri.nim @@ -8,6 +8,35 @@ # ## This module implements URI parsing as specified by RFC 3986. +## +## A Uniform Resource Identifier (URI) provides a simple and extensible +## means for identifying a resource. A URI can be further classified +## as a locator, a name, or both. The term “Uniform Resource Locator” +## (URL) refers to the subset of URIs. +## +## Basic usage +## =========== +## +## Combine URIs +## ------------- +## .. code-block:: +## import uri +## let host = parseUri("https://nim-lang.org") +## let blog = "/blog.html" +## let bloguri = host / blog +## assert $host == "https://nim-lang.org" +## assert $bloguri == "https://nim-lang.org/blog.html" +## +## Access URI item +## --------------- +## .. code-block:: +## import uri +## let res = parseUri("sftp://127.0.0.1:4343") +## if isAbsolute(res): +## echo "Connect to port: " & res.port +## # --> Connect to port: 4343 +## else: +## echo "Wrong format" import strutils, parseutils type @@ -24,11 +53,18 @@ proc encodeUrl*(s: string, usePlus=true): string = ## This means that characters in the set ## ``{'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~'}`` are ## carried over to the result. - ## All other characters are encoded as ``''%xx'`` where ``xx`` + ## All other characters are encoded as ``%xx`` where ``xx`` ## denotes its hexadecimal value. ## ## As a special rule, when the value of ``usePlus`` is true, - ## spaces are encoded as ``'+'`` instead of ``'%20'``. + ## spaces are encoded as ``+`` instead of ``%20``. + ## + ## **See also:** + ## * `decodeUrl proc<#decodeUrl,string>`_ + runnableExamples: + assert encodeUrl("https://nim-lang.org") == "https%3A%2F%2Fnim-lang.org" + assert encodeUrl("https://nim-lang.org/this is a test") == "https%3A%2F%2Fnim-lang.org%2Fthis+is+a+test" + assert encodeUrl("https://nim-lang.org/this is a test", false) == "https%3A%2F%2Fnim-lang.org%2Fthis%20is%20a%20test" result = newStringOfCap(s.len + s.len shr 2) # assume 12% non-alnum-chars let fromSpace = if usePlus: "+" else: "%20" for c in s: @@ -43,12 +79,19 @@ proc encodeUrl*(s: string, usePlus=true): string = proc decodeUrl*(s: string, decodePlus=true): string = ## Decodes a URL according to RFC3986. ## - ## This means that any ``'%xx'`` (where ``xx`` denotes a hexadecimal + ## This means that any ``%xx`` (where ``xx`` denotes a hexadecimal ## value) are converted to the character with ordinal number ``xx``, ## and every other character is carried over. ## - ## As a special rule, when the value of ``decodePlus`` is true, ``'+'`` + ## As a special rule, when the value of ``decodePlus`` is true, ``+`` ## characters are converted to a space. + ## + ## **See also:** + ## * `encodeUrl proc<#encodeUrl,string>`_ + runnableExamples: + assert decodeUrl("https%3A%2F%2Fnim-lang.org") == "https://nim-lang.org" + assert decodeUrl("https%3A%2F%2Fnim-lang.org%2Fthis+is+a+test") == "https://nim-lang.org/this is a test" + assert decodeUrl("https%3A%2F%2Fnim-lang.org%2Fthis%20is%20a%20test", false) == "https://nim-lang.org/this is a test" proc handleHexChar(c: char, x: var int) {.inline.} = case c of '0'..'9': x = (x shl 4) or (ord(c) - ord('0')) @@ -123,7 +166,14 @@ proc parsePath(uri: string, i: var int, result: var Uri) = i.inc parseUntil(uri, result.anchor, {}, i) proc initUri*(): Uri = - ## Initializes a URI. + ## Initializes a URI with ``scheme``, ``username``, ``password``, + ## ``hostname``, ``port``, ``path``, ``query`` and ``anchor``. + ## + ## **See also:** + ## * `Uri type <#Uri>`_ for available fields in the URI type + runnableExamples: + var uri: Uri + assert initUri() == uri result = Uri(scheme: "", username: "", password: "", hostname: "", port: "", path: "", query: "", anchor: "") @@ -136,6 +186,16 @@ proc resetUri(uri: var Uri) = proc parseUri*(uri: string, result: var Uri) = ## Parses a URI. The `result` variable will be cleared before. + ## + ## **See also:** + ## * `Uri type <#Uri>`_ for available fields in the URI type + ## * `initUri proc <#initUri,>`_ for initializing a URI + runnableExamples: + var res = initUri() + parseUri("https://nim-lang.org/docs/manual.html", res) + assert res.scheme == "https" + assert res.hostname == "nim-lang.org" + assert res.path == "/docs/manual.html" resetUri(result) var i = 0 @@ -174,6 +234,14 @@ proc parseUri*(uri: string, result: var Uri) = proc parseUri*(uri: string): Uri = ## Parses a URI and returns it. + ## + ## **See also:** + ## * `Uri type <#Uri>`_ for available fields in the URI type + runnableExamples: + let res = parseUri("ftp://Username:Password@Hostname") + assert res.username == "Username" + assert res.password == "Password" + assert res.scheme == "ftp" result = initUri() parseUri(uri, result) @@ -224,22 +292,18 @@ proc combine*(base: Uri, reference: Uri): Uri = ## This uses the algorithm specified in ## `section 5.2.2 of RFC 3986 `_. ## - ## This means that the slashes inside the base URI's path as well as reference - ## URI's path affect the resulting URI. - ## - ## For building URIs you may wish to use \`/\` instead. - ## - ## Examples: + ## This means that the slashes inside the base URIs path as well as reference + ## URIs path affect the resulting URI. ## - ## .. code-block:: - ## let foo = combine(parseUri("http://example.com/foo/bar"), parseUri("/baz")) - ## assert foo.path == "/baz" - ## - ## let bar = combine(parseUri("http://example.com/foo/bar"), parseUri("baz")) - ## assert bar.path == "/foo/baz" - ## - ## let bar = combine(parseUri("http://example.com/foo/bar/"), parseUri("baz")) - ## assert bar.path == "/foo/bar/baz" + ## **See also:** + ## * `/ proc <#/,Uri,string>`_ for building URIs + runnableExamples: + let foo = combine(parseUri("https://nim-lang.org/foo/bar"), parseUri("/baz")) + assert foo.path == "/baz" + let bar = combine(parseUri("https://nim-lang.org/foo/bar"), parseUri("baz")) + assert bar.path == "/foo/baz" + let qux = combine(parseUri("https://nim-lang.org/foo/bar/"), parseUri("baz")) + assert qux.path == "/foo/bar/baz" template setAuthority(dest, src): untyped = dest.hostname = src.hostname @@ -275,32 +339,42 @@ proc combine*(base: Uri, reference: Uri): Uri = proc combine*(uris: varargs[Uri]): Uri = ## Combines multiple URIs together. + ## + ## **See also:** + ## * `/ proc <#/,Uri,string>`_ for building URIs + runnableExamples: + let foo = combine(parseUri("https://nim-lang.org/blog.html"), parseUri("/install.html")) + assert foo.hostname == "nim-lang.org" + assert foo.path == "/install.html" result = uris[0] for i in 1 ..< uris.len: result = combine(result, uris[i]) proc isAbsolute*(uri: Uri): bool = - ## returns true if URI is absolute, false otherwise + ## Returns true if URI is absolute, false otherwise. + runnableExamples: + let foo = parseUri("https://nim-lang.org") + assert isAbsolute(foo) == true + let bar = parseUri("nim-lang") + assert isAbsolute(bar) == false return uri.scheme != "" and (uri.hostname != "" or uri.path != "") proc `/`*(x: Uri, path: string): Uri = - ## Concatenates the path specified to the specified URI's path. + ## Concatenates the path specified to the specified URIs path. ## - ## Contrary to the ``combine`` procedure you do not have to worry about - ## the slashes at the beginning and end of the path and URI's path + ## Contrary to the `combine proc <#combine,Uri,Uri>`_ you do not have to worry about + ## the slashes at the beginning and end of the path and URIs path ## respectively. ## - ## Examples: - ## - ## .. code-block:: - ## let foo = parseUri("http://example.com/foo/bar") / "/baz" - ## assert foo.path == "/foo/bar/baz" - ## - ## let bar = parseUri("http://example.com/foo/bar") / "baz" - ## assert bar.path == "/foo/bar/baz" - ## - ## let bar = parseUri("http://example.com/foo/bar/") / "baz" - ## assert bar.path == "/foo/bar/baz" + ## **See also:** + ## * `combine proc <#combine,Uri,Uri>`_ + runnableExamples: + let foo = parseUri("https://nim-lang.org/foo/bar") / "/baz" + assert foo.path == "/foo/bar/baz" + let bar = parseUri("https://nim-lang.org/foo/bar") / "baz" + assert bar.path == "/foo/bar/baz" + let qux = parseUri("https://nim-lang.org/foo/bar/") / "baz" + assert qux.path == "/foo/bar/baz" result = x if result.path.len == 0: @@ -321,6 +395,9 @@ proc `/`*(x: Uri, path: string): Uri = proc `$`*(u: Uri): string = ## Returns the string representation of the specified URI object. + runnableExamples: + let foo = parseUri("https://nim-lang.org") + assert $foo == "https://nim-lang.org" result = "" if u.scheme.len > 0: result.add(u.scheme) -- cgit 1.4.1-2-gfad0 From 095eaacf21a80c72d15ba9a7a422ddc192124ae1 Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Sat, 19 Jan 2019 15:01:27 +0000 Subject: Fix spelling errors (#10379) --- compiler/semexprs.nim | 2 +- lib/pure/includes/osseps.nim | 4 ++-- lib/pure/net.nim | 38 +++++++++++++++++++------------------- tests/stdlib/ttimes.nim | 2 +- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 6f2981f63..5e1e4cbbd 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -230,7 +230,7 @@ proc semConv(c: PContext, n: PNode): PNode = # special case to make MyObject(x = 3) produce a nicer error message: if n[1].kind == nkExprEqExpr and targetType.skipTypes(abstractPtrs).kind == tyObject: - localError(c.config, n.info, "object contruction uses ':', not '='") + localError(c.config, n.info, "object construction uses ':', not '='") var op = semExprWithType(c, n.sons[1]) if targetType.isMetaType: let final = inferWithMetatype(c, targetType, op, true) diff --git a/lib/pure/includes/osseps.nim b/lib/pure/includes/osseps.nim index 9a79fe303..944ad123e 100644 --- a/lib/pure/includes/osseps.nim +++ b/lib/pure/includes/osseps.nim @@ -85,9 +85,9 @@ elif doslikeFileSystem: const CurDir* = '.' ParDir* = ".." - DirSep* = '\\' # seperator within paths + DirSep* = '\\' # separator within paths AltSep* = '/' - PathSep* = ';' # seperator between paths + PathSep* = ';' # separator between paths FileSystemCaseSensitive* = false ExeExt* = "exe" ScriptExt* = "bat" diff --git a/lib/pure/net.nim b/lib/pure/net.nim index d4c5a88b7..a5bdf3ce6 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -233,7 +233,7 @@ proc parseIPv4Address(addressStr: string): IpAddress = var byteCount = 0 currentByte:uint16 = 0 - seperatorValid = false + separatorValid = false result.family = IpAddressFamily.IPv4 @@ -244,20 +244,20 @@ proc parseIPv4Address(addressStr: string): IpAddress = if currentByte > 255'u16: raise newException(ValueError, "Invalid IP Address. Value is out of range") - seperatorValid = true + separatorValid = true elif addressStr[i] == '.': # IPv4 address separator - if not seperatorValid or byteCount >= 3: + if not separatorValid or byteCount >= 3: raise newException(ValueError, "Invalid IP Address. The address consists of too many groups") result.address_v4[byteCount] = cast[uint8](currentByte) currentByte = 0 byteCount.inc - seperatorValid = false + separatorValid = false else: raise newException(ValueError, "Invalid IP Address. Address contains an invalid character") - if byteCount != 3 or not seperatorValid: + if byteCount != 3 or not separatorValid: raise newException(ValueError, "Invalid IP Address") result.address_v4[byteCount] = cast[uint8](currentByte) @@ -272,7 +272,7 @@ proc parseIPv6Address(addressStr: string): IpAddress = groupCount = 0 currentGroupStart = 0 currentShort:uint32 = 0 - seperatorValid = true + separatorValid = true dualColonGroup = -1 lastWasColon = false v4StartPos = -1 @@ -280,15 +280,15 @@ proc parseIPv6Address(addressStr: string): IpAddress = for i,c in addressStr: if c == ':': - if not seperatorValid: + if not separatorValid: raise newException(ValueError, - "Invalid IP Address. Address contains an invalid seperator") + "Invalid IP Address. Address contains an invalid separator") if lastWasColon: if dualColonGroup != -1: raise newException(ValueError, - "Invalid IP Address. Address contains more than one \"::\" seperator") + "Invalid IP Address. Address contains more than one \"::\" separator") dualColonGroup = groupCount - seperatorValid = false + separatorValid = false elif i != 0 and i != high(addressStr): if groupCount >= 8: raise newException(ValueError, @@ -297,7 +297,7 @@ proc parseIPv6Address(addressStr: string): IpAddress = result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF) currentShort = 0 groupCount.inc() - if dualColonGroup != -1: seperatorValid = false + if dualColonGroup != -1: separatorValid = false elif i == 0: # only valid if address starts with :: if addressStr[1] != ':': raise newException(ValueError, @@ -309,11 +309,11 @@ proc parseIPv6Address(addressStr: string): IpAddress = lastWasColon = true currentGroupStart = i + 1 elif c == '.': # Switch to parse IPv4 mode - if i < 3 or not seperatorValid or groupCount >= 7: + if i < 3 or not separatorValid or groupCount >= 7: raise newException(ValueError, "Invalid IP Address") v4StartPos = currentGroupStart currentShort = 0 - seperatorValid = false + separatorValid = false break elif c in strutils.HexDigits: if c in strutils.Digits: # Normal digit @@ -326,14 +326,14 @@ proc parseIPv6Address(addressStr: string): IpAddress = raise newException(ValueError, "Invalid IP Address. Value is out of range") lastWasColon = false - seperatorValid = true + separatorValid = true else: raise newException(ValueError, "Invalid IP Address. Address contains an invalid character") if v4StartPos == -1: # Don't parse v4. Copy the remaining v6 stuff - if seperatorValid: # Copy remaining data + if separatorValid: # Copy remaining data if groupCount >= 8: raise newException(ValueError, "Invalid IP Address. The address consists of too many groups") @@ -347,19 +347,19 @@ proc parseIPv6Address(addressStr: string): IpAddress = if currentShort > 255'u32: raise newException(ValueError, "Invalid IP Address. Value is out of range") - seperatorValid = true + separatorValid = true elif c == '.': # IPv4 address separator - if not seperatorValid or byteCount >= 3: + if not separatorValid or byteCount >= 3: raise newException(ValueError, "Invalid IP Address") result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) currentShort = 0 byteCount.inc() - seperatorValid = false + separatorValid = false else: # Invalid character raise newException(ValueError, "Invalid IP Address. Address contains an invalid character") - if byteCount != 3 or not seperatorValid: + if byteCount != 3 or not separatorValid: raise newException(ValueError, "Invalid IP Address") result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) groupCount += 2 diff --git a/tests/stdlib/ttimes.nim b/tests/stdlib/ttimes.nim index 32f39953d..3999c968f 100644 --- a/tests/stdlib/ttimes.nim +++ b/tests/stdlib/ttimes.nim @@ -253,7 +253,7 @@ suite "ttimes": parseTestExcp("+1", "mm") parseTestExcp("+1", "ss") - test "_ as a seperator": + test "_ as a separator": discard parse("2000_01_01", "YYYY'_'MM'_'dd") test "dynamic timezone": -- cgit 1.4.1-2-gfad0 From 28b3c8d74dcc3b812c2a386af68a39d3f746edcc Mon Sep 17 00:00:00 2001 From: Kobi Date: Sat, 19 Jan 2019 14:31:14 +0200 Subject: prevent index out of bounds error in oserr.nim --- lib/pure/includes/oserr.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/includes/oserr.nim b/lib/pure/includes/oserr.nim index 9ae06f5bc..db7d84c1e 100644 --- a/lib/pure/includes/oserr.nim +++ b/lib/pure/includes/oserr.nim @@ -59,7 +59,7 @@ proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} = e.errorCode = errorCode.int32 e.msg = osErrorMsg(errorCode) if additionalInfo.len > 0: - if e.msg[^1] != '\n': e.msg.add '\n' + if e.msg.len > 0 and e.msg[^1] != '\n': e.msg.add '\n' e.msg.add "Additional info: " e.msg.addQuoted additionalInfo if e.msg == "": -- cgit 1.4.1-2-gfad0 From 32a90f74063c3ee238a4909d8678720d93545014 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Sun, 20 Jan 2019 23:24:06 -0800 Subject: fix json bug `[]=` misplaced (#10397) --- lib/pure/json.nim | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 02fa6dc67..cf979e2d0 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -357,6 +357,11 @@ when false: assert false notin elements, "usage error: only empty sets allowed" assert true notin elements, "usage error: only empty sets allowed" +proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) {.inline.} = + ## Sets a field from a `JObject`. + assert(obj.kind == JObject) + obj.fields[key] = val + #[ Note: could use simply: proc `%`*(o: object|tuple): JsonNode @@ -522,11 +527,6 @@ proc contains*(node: JsonNode, val: JsonNode): bool = proc existsKey*(node: JsonNode, key: string): bool {.deprecated: "use hasKey instead".} = node.hasKey(key) ## **Deprecated:** use `hasKey` instead. -proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) {.inline.} = - ## Sets a field from a `JObject`. - assert(obj.kind == JObject) - obj.fields[key] = val - proc `{}`*(node: JsonNode, keys: varargs[string]): JsonNode = ## Traverses the node and gets the given value. If any of the ## keys do not exist, returns ``nil``. Also returns ``nil`` if one of the -- cgit 1.4.1-2-gfad0 From 5491f40d544494d4277d17669ca1fe0713d66f35 Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Mon, 21 Jan 2019 14:35:23 +0530 Subject: fix vccexe not using correct path for detecting vcvarsall (#10364) --- tools/vccexe/vccenv.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/vccexe/vccenv.nim b/tools/vccexe/vccenv.nim index 724fe992a..bd3b0b30a 100644 --- a/tools/vccexe/vccenv.nim +++ b/tools/vccexe/vccenv.nim @@ -16,7 +16,7 @@ type vs140 = (140, "VS140COMNTOOLS") ## Visual Studio 2015 const - vcvarsallRelativePath = joinPath("..", "..", "VC", "vcvarsall") ## Relative path from the COMNTOOLS path to the vcvarsall file. + vcvarsallRelativePath = joinPath("..", "..", "VC", "vcvarsall.bat") ## Relative path from the COMNTOOLS path to the vcvarsall file. proc vccEnvVcVarsAllPath*(version: VccEnvVersion = vsUndefined): string = ## Returns the path to the VCC Developer Command Prompt executable for the specified VCC version. @@ -44,4 +44,4 @@ proc vccEnvVcVarsAllPath*(version: VccEnvVersion = vsUndefined): string = let key = $version let val = getEnv key if val.len > 0: - result = try: expandFilename(val & vcvarsallRelativePath) except OSError: "" + result = try: expandFilename(joinPath(val, vcvarsallRelativePath)) except OSError: "" -- cgit 1.4.1-2-gfad0 From 9a003bae06014beb017c7a77b351f676cee6ce5d Mon Sep 17 00:00:00 2001 From: Oscar Nihlgård Date: Mon, 21 Jan 2019 15:14:38 +0100 Subject: Fix error lexer error messages for to large numbers (#10394) --- compiler/lexer.nim | 62 +++++++++++++++++-------------------- tests/errmsgs/tinteger_literals.nim | 15 +++++++++ 2 files changed, 44 insertions(+), 33 deletions(-) create mode 100644 tests/errmsgs/tinteger_literals.nim diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 635e6f08d..a4414d186 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -17,7 +17,7 @@ import hashes, options, msgs, strutils, platform, idents, nimlexbase, llstream, - wordrecg, lineinfos, pathutils + wordrecg, lineinfos, pathutils, parseutils const MaxLineLength* = 80 # lines longer than this lead to a warning @@ -307,20 +307,6 @@ template tokenEndPrevious(tok, pos) = when defined(nimpretty): tok.offsetB = L.offsetBase + pos -{.push overflowChecks: off.} -# We need to parse the largest uint literal without overflow checks -proc unsafeParseUInt(s: string, b: var BiggestInt, start = 0): int = - var i = start - if i < s.len and s[i] in {'0'..'9'}: - b = 0 - while i < s.len and s[i] in {'0'..'9'}: - b = b * 10 + (ord(s[i]) - ord('0')) - inc(i) - while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored - result = i - start -{.pop.} # overflowChecks - - template eatChar(L: var TLexer, t: var TToken, replacementChar: char) = add(t.literal, replacementChar) inc(L.bufpos) @@ -586,33 +572,43 @@ proc getNumber(L: var TLexer, result: var TToken) = of floatTypes: result.fNumber = parseFloat(result.literal) of tkUint64Lit: - xi = 0 - let len = unsafeParseUInt(result.literal, xi) - if len != result.literal.len or len == 0: - raise newException(ValueError, "invalid integer: " & $xi) - result.iNumber = xi + var iNumber: uint64 + var len: int + try: + len = parseBiggestUInt(result.literal, iNumber) + except ValueError: + raise newException(OverflowError, "number out of range: " & $result.literal) + if len != result.literal.len: + raise newException(ValueError, "invalid integer: " & $result.literal) + result.iNumber = cast[int64](iNumber) else: - result.iNumber = parseBiggestInt(result.literal) - - # Explicit bounds checks + var iNumber: int64 + var len: int + try: + len = parseBiggestInt(result.literal, iNumber) + except ValueError: + raise newException(OverflowError, "number out of range: " & $result.literal) + if len != result.literal.len: + raise newException(ValueError, "invalid integer: " & $result.literal) + result.iNumber = iNumber + + # Explicit bounds checks. Only T.high needs to be considered + # since result.iNumber can't be negative. let outOfRange = case result.tokType - of tkInt8Lit: (result.iNumber < int8.low or result.iNumber > int8.high) - of tkUInt8Lit: (result.iNumber < BiggestInt(uint8.low) or - result.iNumber > BiggestInt(uint8.high)) - of tkInt16Lit: (result.iNumber < int16.low or result.iNumber > int16.high) - of tkUInt16Lit: (result.iNumber < BiggestInt(uint16.low) or - result.iNumber > BiggestInt(uint16.high)) - of tkInt32Lit: (result.iNumber < int32.low or result.iNumber > int32.high) - of tkUInt32Lit: (result.iNumber < BiggestInt(uint32.low) or - result.iNumber > BiggestInt(uint32.high)) + of tkInt8Lit: result.iNumber > int8.high + of tkUInt8Lit: result.iNumber > BiggestInt(uint8.high) + of tkInt16Lit: result.iNumber > int16.high + of tkUInt16Lit: result.iNumber > BiggestInt(uint16.high) + of tkInt32Lit: result.iNumber > int32.high + of tkUInt32Lit: result.iNumber > BiggestInt(uint32.high) else: false if outOfRange: lexMessageLitNum(L, "number out of range: '$1'", startpos) # Promote int literal to int64? Not always necessary, but more consistent if result.tokType == tkIntLit: - if (result.iNumber < low(int32)) or (result.iNumber > high(int32)): + if result.iNumber > high(int32): result.tokType = tkInt64Lit except ValueError: diff --git a/tests/errmsgs/tinteger_literals.nim b/tests/errmsgs/tinteger_literals.nim new file mode 100644 index 000000000..98c92a227 --- /dev/null +++ b/tests/errmsgs/tinteger_literals.nim @@ -0,0 +1,15 @@ +discard """ +cmd: "nim check $file" +errormsg: "number out of range: '300'u8'" +nimout: ''' +tinteger_literals.nim(12, 9) Error: number out of range: '18446744073709551616'u64' +tinteger_literals.nim(13, 9) Error: number out of range: '9223372036854775808'i64' +tinteger_literals.nim(14, 9) Error: number out of range: '9223372036854775808' +tinteger_literals.nim(15, 9) Error: number out of range: '300'u8' +''' +""" + +discard 18446744073709551616'u64 # high(uint64) + 1 +discard 9223372036854775808'i64 # high(int64) + 1 +discard 9223372036854775808 # high(int64) + 1 +discard 300'u8 \ No newline at end of file -- cgit 1.4.1-2-gfad0 From 4bea8dd674c87b49195220bd6c5d72def467d8e5 Mon Sep 17 00:00:00 2001 From: Miran Date: Mon, 21 Jan 2019 15:22:53 +0100 Subject: better docs for lists and deques (#10390) * better docs: lists * better docs: deques --- lib/pure/collections/deques.nim | 298 +++++++++++++++++++--- lib/pure/collections/lists.nim | 533 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 755 insertions(+), 76 deletions(-) diff --git a/lib/pure/collections/deques.nim b/lib/pure/collections/deques.nim index 5243b96a6..cb05e5112 100644 --- a/lib/pure/collections/deques.nim +++ b/lib/pure/collections/deques.nim @@ -20,41 +20,59 @@ ## 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 +## import deques ## -## if b < deq.len: # checking before indexed access -## echo "The element at index position ", b, " is ", deq[b] +## var a = initDeque[int]() ## -## # 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 +## doAssertRaises(IndexError, echo a[0]) ## -## while deq.len > 0: # checking if the deque is empty -## echo deq.popLast() +## for i in 1 .. 5: +## a.addLast(10*i) +## assert $a == "[10, 20, 30, 40, 50]" ## -## Note: For inter thread communication use -## a `Channel `_ instead. +## assert a.peekFirst == 10 +## assert a.peekLast == 50 +## assert len(a) == 5 +## +## assert a.popFirst == 10 +## assert a.popLast == 50 +## assert len(a) == 3 +## +## a.addFirst(11) +## a.addFirst(22) +## a.addFirst(33) +## assert $a == "[33, 22, 11, 20, 30, 40]" +## +## a.shrink(fromFirst = 1, fromLast = 2) +## assert $a == "[22, 11, 20]" +## +## +## **See also:** +## * `lists module `_ for singly and doubly linked lists and rings +## * `channels module `_ for inter-thread communication + import math, typetraits type Deque*[T] = object ## A double-ended queue backed with a ringed seq buffer. + ## + ## To initialize an empty deque use `initDeque proc <#initDeque,int>`_. 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. + ## Create a new empty 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. + ## ``initialSize`` must be a power of two (default: 4). + ## 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) @@ -75,45 +93,128 @@ template xBoundsCheck(deq, i) = 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)) + if unlikely(i < 0): # when used with BackwardsIndex + raise newException(IndexError, + "Out of bounds: " & $i & " < 0") 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. + ## Access the i-th element of `deq`. + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert a[0] == 10 + assert a[3] == 40 + doAssertRaises(IndexError, echo a[8]) + xBoundsCheck(deq, i) return deq.data[(deq.head + 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 + ## Access the i-th element of `deq` and return a mutable ## reference to it. + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert a[0] == 10 + assert a[3] == 40 + doAssertRaises(IndexError, echo a[8]) + 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`. + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + a[0] = 99 + a[3] = 66 + assert $a == "[99, 20, 30, 66, 50]" + xBoundsCheck(deq, i) deq.data[(deq.head + i) and deq.mask] = val -proc `[]`*[T](deq: var Deque[T], i: BackwardsIndex): var T {.inline.} = +proc `[]`*[T](deq: Deque[T], i: BackwardsIndex): T {.inline.} = ## Access the backwards indexed i-th element. + ## + ## `deq[^1]` is the last element. + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert a[^1] == 50 + assert a[^4] == 20 + doAssertRaises(IndexError, echo a[^9]) + + xBoundsCheck(deq, deq.len - int(i)) return deq[deq.len - int(i)] -proc `[]`*[T](deq: Deque[T], i: BackwardsIndex): T {.inline.} = +proc `[]`*[T](deq: var Deque[T], i: BackwardsIndex): var T {.inline.} = ## Access the backwards indexed i-th element. + ## + ## `deq[^1]` is the last element. + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert a[^1] == 50 + assert a[^4] == 20 + doAssertRaises(IndexError, echo a[^9]) + + xBoundsCheck(deq, deq.len - int(i)) return deq[deq.len - int(i)] proc `[]=`*[T](deq: var Deque[T], i: BackwardsIndex, x: T) {.inline.} = ## Change the backwards indexed i-th element. + ## + ## `deq[^1]` is the last element. + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + a[^1] = 99 + a[^3] = 77 + assert $a == "[10, 20, 77, 40, 99]" + + xBoundsCheck(deq, deq.len - int(i)) deq[deq.len - int(i)] = x iterator items*[T](deq: Deque[T]): T = ## Yield every element of `deq`. + ## + ## **Examples:** + ## + ## .. code-block:: + ## var a = initDeque[int]() + ## for i in 1 .. 3: + ## a.addLast(10*i) + ## + ## for x in a: # the same as: for x in items(a): + ## echo x + ## + ## # 10 + ## # 20 + ## # 30 + ## 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`. + ## Yield every element of `deq`, which can be modified. + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert $a == "[10, 20, 30, 40, 50]" + for x in mitems(a): + x = 5*x - 1 + assert $a == "[49, 99, 149, 199, 249]" + var i = deq.head for c in 0 ..< deq.count: yield deq.data[i] @@ -121,18 +222,35 @@ iterator mitems*[T](deq: var Deque[T]): var T = iterator pairs*[T](deq: Deque[T]): tuple[key: int, val: T] = ## Yield every (position, value) of `deq`. + ## + ## **Examples:** + ## + ## .. code-block:: + ## var a = initDeque[int]() + ## for i in 1 .. 3: + ## a.addLast(10*i) + ## + ## for k, v in pairs(a): + ## echo "key: ", k, ", value: ", v + ## + ## # key: 0, value: 10 + ## # key: 1, value: 20 + ## # key: 2, value: 30 + ## 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``. + ## 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 + ## assert q.contains(x) for e in deq: if e == item: return true return false @@ -150,6 +268,19 @@ proc expandIfNeeded[T](deq: var Deque[T]) = proc addFirst*[T](deq: var Deque[T], item: T) = ## Add an `item` to the beginning of the `deq`. + ## + ## See also: + ## * `addLast proc <#addLast,Deque[T],T>`_ + ## * `peekFirst proc <#peekFirst,Deque[T]>`_ + ## * `peekLast proc <#peekLast,Deque[T]>`_ + ## * `popFirst proc <#popFirst,Deque[T]>`_ + ## * `popLast proc <#popLast,Deque[T]>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addFirst(10*i) + assert $a == "[50, 40, 30, 20, 10]" + expandIfNeeded(deq) inc deq.count deq.head = (deq.head - 1) and deq.mask @@ -157,6 +288,19 @@ proc addFirst*[T](deq: var Deque[T], item: T) = proc addLast*[T](deq: var Deque[T], item: T) = ## Add an `item` to the end of the `deq`. + ## + ## See also: + ## * `addFirst proc <#addFirst,Deque[T],T>`_ + ## * `peekFirst proc <#peekFirst,Deque[T]>`_ + ## * `peekLast proc <#peekLast,Deque[T]>`_ + ## * `popFirst proc <#popFirst,Deque[T]>`_ + ## * `popLast proc <#popLast,Deque[T]>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert $a == "[10, 20, 30, 40, 50]" + expandIfNeeded(deq) inc deq.count deq.data[deq.tail] = item @@ -164,11 +308,41 @@ proc addLast*[T](deq: var Deque[T], item: T) = proc peekFirst*[T](deq: Deque[T]): T {.inline.}= ## Returns the first element of `deq`, but does not remove it from the deque. + ## + ## See also: + ## * `addFirst proc <#addFirst,Deque[T],T>`_ + ## * `addLast proc <#addLast,Deque[T],T>`_ + ## * `peekLast proc <#peekLast,Deque[T]>`_ + ## * `popFirst proc <#popFirst,Deque[T]>`_ + ## * `popLast proc <#popLast,Deque[T]>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert $a == "[10, 20, 30, 40, 50]" + assert a.peekFirst == 10 + assert len(a) == 5 + 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. + ## + ## See also: + ## * `addFirst proc <#addFirst,Deque[T],T>`_ + ## * `addLast proc <#addLast,Deque[T],T>`_ + ## * `peekFirst proc <#peekFirst,Deque[T]>`_ + ## * `popFirst proc <#popFirst,Deque[T]>`_ + ## * `popLast proc <#popLast,Deque[T]>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert $a == "[10, 20, 30, 40, 50]" + assert a.peekLast == 50 + assert len(a) == 5 + emptyCheck(deq) result = deq.data[(deq.tail - 1) and deq.mask] @@ -177,6 +351,23 @@ template destroy(x: untyped) = proc popFirst*[T](deq: var Deque[T]): T {.inline, discardable.} = ## Remove and returns the first element of the `deq`. + ## + ## See also: + ## * `addFirst proc <#addFirst,Deque[T],T>`_ + ## * `addLast proc <#addLast,Deque[T],T>`_ + ## * `peekFirst proc <#peekFirst,Deque[T]>`_ + ## * `peekLast proc <#peekLast,Deque[T]>`_ + ## * `popLast proc <#popLast,Deque[T]>`_ + ## * `clear proc <#clear,Deque[T]>`_ + ## * `shrink proc <#shrink,Deque[T],int,int>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert $a == "[10, 20, 30, 40, 50]" + assert a.popFirst == 10 + assert $a == "[20, 30, 40, 50]" + emptyCheck(deq) dec deq.count result = deq.data[deq.head] @@ -185,6 +376,23 @@ proc popFirst*[T](deq: var Deque[T]): T {.inline, discardable.} = proc popLast*[T](deq: var Deque[T]): T {.inline, discardable.} = ## Remove and returns the last element of the `deq`. + ## + ## See also: + ## * `addFirst proc <#addFirst,Deque[T],T>`_ + ## * `addLast proc <#addLast,Deque[T],T>`_ + ## * `peekFirst proc <#peekFirst,Deque[T]>`_ + ## * `peekLast proc <#peekLast,Deque[T]>`_ + ## * `popFirst proc <#popFirst,Deque[T]>`_ + ## * `clear proc <#clear,Deque[T]>`_ + ## * `shrink proc <#shrink,Deque[T],int,int>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert $a == "[10, 20, 30, 40, 50]" + assert a.popLast == 50 + assert $a == "[10, 20, 30, 40]" + emptyCheck(deq) dec deq.count deq.tail = (deq.tail - 1) and deq.mask @@ -193,17 +401,39 @@ proc popLast*[T](deq: var Deque[T]): T {.inline, discardable.} = proc clear*[T](deq: var Deque[T]) {.inline.} = ## Resets the deque so that it is empty. + ## + ## See also: + ## * `clear proc <#clear,Deque[T]>`_ + ## * `shrink proc <#shrink,Deque[T],int,int>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addFirst(10*i) + assert $a == "[50, 40, 30, 20, 10]" + clear(a) + assert len(a) == 0 + for el in mitems(deq): destroy(el) deq.count = 0 deq.tail = deq.head proc shrink*[T](deq: var Deque[T], fromFirst = 0, fromLast = 0) = ## Remove `fromFirst` elements from the front of the deque and - ## `fromLast` elements from the back. If the supplied number of - ## elements exceeds the total number of elements in the deque, - ## the deque will remain empty. + ## `fromLast` elements from the back. + ## + ## If the supplied number of elements exceeds the total number of elements + ## in the deque, the deque will remain empty. ## - ## Any user defined destructors + ## See also: + ## * `clear proc <#clear,Deque[T]>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addFirst(10*i) + assert $a == "[50, 40, 30, 20, 10]" + a.shrink(fromFirst = 2, fromLast = 1) + assert $a == "[30, 20]" + if fromFirst + fromLast > deq.count: clear(deq) return @@ -226,6 +456,8 @@ proc `$`*[T](deq: Deque[T]): string = result.addQuoted(x) result.add("]") + + when isMainModule: var deq = initDeque[int](1) deq.addLast(4) diff --git a/lib/pure/collections/lists.nim b/lib/pure/collections/lists.nim index 15ce5d074..182eb8442 100644 --- a/lib/pure/collections/lists.nim +++ b/lib/pure/collections/lists.nim @@ -7,34 +7,112 @@ # distribution, for details about the copyright. # -## Implementation of singly and doubly linked lists. Because it makes no sense -## to do so, the 'next' and 'prev' pointers are not hidden from you and can -## be manipulated directly for efficiency. +## Implementation of: +## * `singly linked lists <#SinglyLinkedList>`_ +## * `doubly linked lists <#DoublyLinkedList>`_ +## * `singly linked rings <#SinglyLinkedRing>`_ (circular lists) +## * `doubly linked rings <#DoublyLinkedRing>`_ (circular lists) +## +## +## Basic Usage +## =========== +## +## Because it makes no sense to do otherwise, the `next` and `prev` pointers +## are not hidden from you and can be manipulated directly for efficiency. +## +## Lists +## ----- +## +## .. code-block:: +## import lists +## +## var +## l = initDoublyLinkedList[int]() +## a = newDoublyLinkedNode[int](3) +## b = newDoublyLinkedNode[int](7) +## c = newDoublyLinkedNode[int](9) +## +## l.append(a) +## l.append(b) +## l.prepend(c) +## +## assert a.next == b +## assert a.prev == c +## assert c.next == a +## assert c.next.next == b +## assert c.prev == nil +## assert b.next == nil +## +## +## Rings +## ----- +## +## .. code-block:: +## import lists +## +## var +## l = initSinglyLinkedRing[int]() +## a = newSinglyLinkedNode[int](3) +## b = newSinglyLinkedNode[int](7) +## c = newSinglyLinkedNode[int](9) +## +## l.append(a) +## l.append(b) +## l.prepend(c) +## +## assert c.next == a +## assert a.next == b +## assert c.next.next == b +## assert b.next == c +## assert c.next.next.next == c +## +## See also +## ======== +## +## * `deques module <#deques.html>`_ for double-ended queues +## * `sharedlist module <#sharedlist.html>`_ for shared singly-linked lists + when not defined(nimhygiene): {.pragma: dirty.} type - DoublyLinkedNodeObj*[T] = object ## a node a doubly linked list consists of + DoublyLinkedNodeObj*[T] = object ## A node a doubly linked list consists of. + ## + ## It consists of a `value` field, and pointers to `next` and `prev`. next*, prev*: ref DoublyLinkedNodeObj[T] value*: T DoublyLinkedNode*[T] = ref DoublyLinkedNodeObj[T] - SinglyLinkedNodeObj*[T] = object ## a node a singly linked list consists of + SinglyLinkedNodeObj*[T] = object ## A node a singly linked list consists of. + ## + ## It consists of a `value` field, and a pointer to `next`. next*: ref SinglyLinkedNodeObj[T] value*: T SinglyLinkedNode*[T] = ref SinglyLinkedNodeObj[T] - SinglyLinkedList*[T] = object ## a singly linked list + SinglyLinkedList*[T] = object ## A singly linked list. + ## + ## Use `initSinglyLinkedList proc <#initSinglyLinkedList,>`_ to create + ## a new empty list. head*, tail*: SinglyLinkedNode[T] - DoublyLinkedList*[T] = object ## a doubly linked list + DoublyLinkedList*[T] = object ## A doubly linked list. + ## + ## Use `initDoublyLinkedList proc <#initDoublyLinkedList,>`_ to create + ## a new empty list. head*, tail*: DoublyLinkedNode[T] - SinglyLinkedRing*[T] = object ## a singly linked ring + SinglyLinkedRing*[T] = object ## A singly linked ring. + ## + ## Use `initSinglyLinkedRing proc <#initSinglyLinkedRing,>`_ to create + ## a new empty ring. head*, tail*: SinglyLinkedNode[T] - DoublyLinkedRing*[T] = object ## a doubly linked ring + DoublyLinkedRing*[T] = object ## A doubly linked ring. + ## + ## Use `initDoublyLinkedRing proc <#initDoublyLinkedRing,>`_ to create + ## a new empty ring. head*: DoublyLinkedNode[T] SomeLinkedList*[T] = SinglyLinkedList[T] | DoublyLinkedList[T] @@ -46,28 +124,44 @@ type SomeLinkedNode*[T] = SinglyLinkedNode[T] | DoublyLinkedNode[T] proc initSinglyLinkedList*[T](): SinglyLinkedList[T] = - ## creates a new singly linked list that is empty. + ## Creates a new singly linked list that is empty. + runnableExamples: + var a = initSinglyLinkedList[int]() discard proc initDoublyLinkedList*[T](): DoublyLinkedList[T] = - ## creates a new doubly linked list that is empty. + ## Creates a new doubly linked list that is empty. + runnableExamples: + var a = initDoublyLinkedList[int]() discard proc initSinglyLinkedRing*[T](): SinglyLinkedRing[T] = - ## creates a new singly linked ring that is empty. + ## Creates a new singly linked ring that is empty. + runnableExamples: + var a = initSinglyLinkedRing[int]() discard proc initDoublyLinkedRing*[T](): DoublyLinkedRing[T] = - ## creates a new doubly linked ring that is empty. + ## Creates a new doubly linked ring that is empty. + runnableExamples: + var a = initDoublyLinkedRing[int]() discard proc newDoublyLinkedNode*[T](value: T): DoublyLinkedNode[T] = - ## creates a new doubly linked node with the given `value`. + ## Creates a new doubly linked node with the given `value`. + runnableExamples: + var n = newDoublyLinkedNode[int](5) + assert n.value == 5 + new(result) result.value = value proc newSinglyLinkedNode*[T](value: T): SinglyLinkedNode[T] = - ## creates a new singly linked node with the given `value`. + ## Creates a new singly linked node with the given `value`. + runnableExamples: + var n = newSinglyLinkedNode[int](5) + assert n.value == 5 + new(result) result.value = value @@ -86,24 +180,100 @@ template itemsRingImpl() {.dirty.} = if it == L.head: break iterator items*[T](L: SomeLinkedList[T]): T = - ## yields every value of `L`. + ## Yields every value of `L`. + ## + ## See also: + ## * `mitems iterator <#mitems.i,SomeLinkedList[T]>`_ + ## * `nodes iterator <#nodes.i,SomeLinkedList[T]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## var a = initSinglyLinkedList[int]() + ## for i in 1 .. 3: + ## a.append(10*i) + ## + ## for x in a: # the same as: for x in items(a): + ## echo x + ## + ## # 10 + ## # 20 + ## # 30 itemsListImpl() iterator items*[T](L: SomeLinkedRing[T]): T = - ## yields every value of `L`. + ## Yields every value of `L`. + ## + ## See also: + ## * `mitems iterator <#mitems.i,SomeLinkedRing[T]>`_ + ## * `nodes iterator <#nodes.i,SomeLinkedRing[T]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## var a = initSinglyLinkedRing[int]() + ## for i in 1 .. 3: + ## a.append(10*i) + ## + ## for x in a: # the same as: for x in items(a): + ## echo x + ## + ## # 10 + ## # 20 + ## # 30 itemsRingImpl() iterator mitems*[T](L: var SomeLinkedList[T]): var T = - ## yields every value of `L` so that you can modify it. + ## Yields every value of `L` so that you can modify it. + ## + ## See also: + ## * `items iterator <#items.i,SomeLinkedList[T]>`_ + ## * `nodes iterator <#nodes.i,SomeLinkedList[T]>`_ + runnableExamples: + var a = initSinglyLinkedList[int]() + for i in 1 .. 5: + a.append(10*i) + assert $a == "[10, 20, 30, 40, 50]" + for x in mitems(a): + x = 5*x - 1 + assert $a == "[49, 99, 149, 199, 249]" itemsListImpl() iterator mitems*[T](L: var SomeLinkedRing[T]): var T = - ## yields every value of `L` so that you can modify it. + ## Yields every value of `L` so that you can modify it. + ## + ## See also: + ## * `items iterator <#items.i,SomeLinkedRing[T]>`_ + ## * `nodes iterator <#nodes.i,SomeLinkedRing[T]>`_ + runnableExamples: + var a = initSinglyLinkedRing[int]() + for i in 1 .. 5: + a.append(10*i) + assert $a == "[10, 20, 30, 40, 50]" + for x in mitems(a): + x = 5*x - 1 + assert $a == "[49, 99, 149, 199, 249]" itemsRingImpl() iterator nodes*[T](L: SomeLinkedList[T]): SomeLinkedNode[T] = - ## iterates over every node of `x`. Removing the current node from the + ## Iterates over every node of `x`. Removing the current node from the ## list during traversal is supported. + ## + ## See also: + ## * `items iterator <#items.i,SomeLinkedList[T]>`_ + ## * `mitems iterator <#mitems.i,SomeLinkedList[T]>`_ + runnableExamples: + var a = initDoublyLinkedList[int]() + for i in 1 .. 5: + a.append(10*i) + assert $a == "[10, 20, 30, 40, 50]" + for x in nodes(a): + if x.value == 30: + a.remove(x) + else: + x.value = 5*x.value - 1 + assert $a == "[49, 99, 199, 249]" + var it = L.head while it != nil: var nxt = it.next @@ -111,8 +281,24 @@ iterator nodes*[T](L: SomeLinkedList[T]): SomeLinkedNode[T] = it = nxt iterator nodes*[T](L: SomeLinkedRing[T]): SomeLinkedNode[T] = - ## iterates over every node of `x`. Removing the current node from the + ## Iterates over every node of `x`. Removing the current node from the ## list during traversal is supported. + ## + ## See also: + ## * `items iterator <#items.i,SomeLinkedRing[T]>`_ + ## * `mitems iterator <#mitems.i,SomeLinkedRing[T]>`_ + runnableExamples: + var a = initDoublyLinkedRing[int]() + for i in 1 .. 5: + a.append(10*i) + assert $a == "[10, 20, 30, 40, 50]" + for x in nodes(a): + if x.value == 30: + a.remove(x) + else: + x.value = 5*x.value - 1 + assert $a == "[49, 99, 199, 249]" + var it = L.head if it != nil: while true: @@ -122,7 +308,7 @@ iterator nodes*[T](L: SomeLinkedRing[T]): SomeLinkedNode[T] = if it == L.head: break proc `$`*[T](L: SomeLinkedCollection[T]): string = - ## turns a list into its string representation. + ## Turns a list into its string representation for logging and printing. result = "[" for x in nodes(L): if result.len > 1: result.add(", ") @@ -130,19 +316,54 @@ proc `$`*[T](L: SomeLinkedCollection[T]): string = result.add("]") proc find*[T](L: SomeLinkedCollection[T], value: T): SomeLinkedNode[T] = - ## searches in the list for a value. Returns nil if the value does not + ## Searches in the list for a value. Returns `nil` if the value does not ## exist. + ## + ## See also: + ## * `contains proc <#contains,SomeLinkedCollection[T],T>`_ + runnableExamples: + var a = initSinglyLinkedList[int]() + a.append(9) + a.append(8) + assert a.find(9).value == 9 + assert a.find(1) == nil + for x in nodes(L): if x.value == value: return x proc contains*[T](L: SomeLinkedCollection[T], value: T): bool {.inline.} = - ## searches in the list for a value. Returns false if the value does not - ## exist, true otherwise. + ## Searches in the list for a value. Returns `false` if the value does not + ## exist, `true` otherwise. + ## + ## See also: + ## * `find proc <#find,SomeLinkedCollection[T],T>`_ + runnableExamples: + var a = initSinglyLinkedList[int]() + a.append(9) + a.append(8) + assert a.contains(9) + assert 8 in a + assert(not a.contains(1)) + assert 2 notin a + result = find(L, value) != nil proc append*[T](L: var SinglyLinkedList[T], n: SinglyLinkedNode[T]) {.inline.} = - ## appends a node `n` to `L`. Efficiency: O(1). + ## Appends (adds to the end) a node `n` to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedList[T],T>`_ for appending a value + ## * `prepend proc <#prepend,SinglyLinkedList[T],SinglyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,SinglyLinkedList[T],T>`_ for prepending a value + runnableExamples: + var + a = initSinglyLinkedList[int]() + n = newSinglyLinkedNode[int](9) + a.append(n) + assert a.contains(9) + n.next = nil if L.tail != nil: assert(L.tail.next == nil) @@ -151,22 +372,75 @@ proc append*[T](L: var SinglyLinkedList[T], if L.head == nil: L.head = n proc append*[T](L: var SinglyLinkedList[T], value: T) {.inline.} = - ## appends a value to `L`. Efficiency: O(1). + ## Appends (adds to the end) a value to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedList[T],T>`_ for appending a value + ## * `prepend proc <#prepend,SinglyLinkedList[T],SinglyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,SinglyLinkedList[T],T>`_ for prepending a value + runnableExamples: + var a = initSinglyLinkedList[int]() + a.append(9) + a.append(8) + assert a.contains(9) append(L, newSinglyLinkedNode(value)) proc prepend*[T](L: var SinglyLinkedList[T], n: SinglyLinkedNode[T]) {.inline.} = - ## prepends a node to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a node to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedList[T],SinglyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,SinglyLinkedList[T],T>`_ for appending a value + ## * `prepend proc <#prepend,SinglyLinkedList[T],T>`_ for prepending a value + runnableExamples: + var + a = initSinglyLinkedList[int]() + n = newSinglyLinkedNode[int](9) + a.prepend(n) + assert a.contains(9) + n.next = L.head L.head = n if L.tail == nil: L.tail = n proc prepend*[T](L: var SinglyLinkedList[T], value: T) {.inline.} = - ## prepends a node to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a node to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedList[T],SinglyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,SinglyLinkedList[T],T>`_ for appending a value + ## * `prepend proc <#prepend,SinglyLinkedList[T],SinglyLinkedNode[T]>`_ + ## for prepending a node + runnableExamples: + var a = initSinglyLinkedList[int]() + a.prepend(9) + a.prepend(8) + assert a.contains(9) prepend(L, newSinglyLinkedNode(value)) + + proc append*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) = - ## appends a node `n` to `L`. Efficiency: O(1). + ## Appends (adds to the end) a node `n` to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedList[T],T>`_ for appending a value + ## * `prepend proc <#prepend,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,DoublyLinkedList[T],T>`_ for prepending a value + ## * `remove proc <#remove,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var + a = initDoublyLinkedList[int]() + n = newDoublyLinkedNode[int](9) + a.append(n) + assert a.contains(9) + n.next = nil n.prev = L.tail if L.tail != nil: @@ -176,11 +450,40 @@ proc append*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) = if L.head == nil: L.head = n proc append*[T](L: var DoublyLinkedList[T], value: T) = - ## appends a value to `L`. Efficiency: O(1). + ## Appends (adds to the end) a value to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for appending a node + ## * `prepend proc <#prepend,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,DoublyLinkedList[T],T>`_ for prepending a value + ## * `remove proc <#remove,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var a = initDoublyLinkedList[int]() + a.append(9) + a.append(8) + assert a.contains(9) append(L, newDoublyLinkedNode(value)) proc prepend*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) = - ## prepends a node `n` to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a node `n` to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,DoublyLinkedList[T],T>`_ for appending a value + ## * `prepend proc <#prepend,DoublyLinkedList[T],T>`_ for prepending a value + ## * `remove proc <#remove,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var + a = initDoublyLinkedList[int]() + n = newDoublyLinkedNode[int](9) + a.prepend(n) + assert a.contains(9) + n.prev = nil n.next = L.head if L.head != nil: @@ -190,18 +493,56 @@ proc prepend*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) = if L.tail == nil: L.tail = n proc prepend*[T](L: var DoublyLinkedList[T], value: T) = - ## prepends a value to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a value to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,DoublyLinkedList[T],T>`_ for appending a value + ## * `prepend proc <#prepend,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for prepending a node + ## * `remove proc <#remove,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var a = initDoublyLinkedList[int]() + a.prepend(9) + a.prepend(8) + assert a.contains(9) prepend(L, newDoublyLinkedNode(value)) proc remove*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) = - ## removes `n` from `L`. Efficiency: O(1). + ## Removes a node `n` from `L`. Efficiency: O(1). + runnableExamples: + var + a = initDoublyLinkedList[int]() + n = newDoublyLinkedNode[int](5) + a.append(n) + assert 5 in a + a.remove(n) + assert 5 notin a + if n == L.tail: L.tail = n.prev if n == L.head: L.head = n.next if n.next != nil: n.next.prev = n.prev if n.prev != nil: n.prev.next = n.next + + proc append*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) = - ## appends a node `n` to `L`. Efficiency: O(1). + ## Appends (adds to the end) a node `n` to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedRing[T],T>`_ for appending a value + ## * `prepend proc <#prepend,SinglyLinkedRing[T],SinglyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,SinglyLinkedRing[T],T>`_ for prepending a value + runnableExamples: + var + a = initSinglyLinkedRing[int]() + n = newSinglyLinkedNode[int](9) + a.append(n) + assert a.contains(9) + if L.head != nil: n.next = L.head assert(L.tail != nil) @@ -213,11 +554,36 @@ proc append*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) = L.tail = n proc append*[T](L: var SinglyLinkedRing[T], value: T) = - ## appends a value to `L`. Efficiency: O(1). + ## Appends (adds to the end) a value to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedRing[T],SinglyLinkedNode[T]>`_ + ## for appending a node + ## * `prepend proc <#prepend,SinglyLinkedRing[T],SinglyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,SinglyLinkedRing[T],T>`_ for prepending a value + runnableExamples: + var a = initSinglyLinkedRing[int]() + a.append(9) + a.append(8) + assert a.contains(9) append(L, newSinglyLinkedNode(value)) proc prepend*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) = - ## prepends a node `n` to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a node `n` to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedRing[T],SinglyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,SinglyLinkedRing[T],T>`_ for appending a value + ## * `prepend proc <#prepend,SinglyLinkedRing[T],T>`_ for prepending a value + runnableExamples: + var + a = initSinglyLinkedRing[int]() + n = newSinglyLinkedNode[int](9) + a.prepend(n) + assert a.contains(9) + if L.head != nil: n.next = L.head assert(L.tail != nil) @@ -228,11 +594,40 @@ proc prepend*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) = L.head = n proc prepend*[T](L: var SinglyLinkedRing[T], value: T) = - ## prepends a value to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a value to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedRing[T],SinglyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,SinglyLinkedRing[T],T>`_ for appending a value + ## * `prepend proc <#prepend,SinglyLinkedRing[T],SinglyLinkedNode[T]>`_ + ## for prepending a node + runnableExamples: + var a = initSinglyLinkedRing[int]() + a.prepend(9) + a.prepend(8) + assert a.contains(9) prepend(L, newSinglyLinkedNode(value)) + + proc append*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) = - ## appends a node `n` to `L`. Efficiency: O(1). + ## Appends (adds to the end) a node `n` to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedRing[T],T>`_ for appending a value + ## * `prepend proc <#prepend,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,DoublyLinkedRing[T],T>`_ for prepending a value + ## * `remove proc <#remove,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var + a = initDoublyLinkedRing[int]() + n = newDoublyLinkedNode[int](9) + a.append(n) + assert a.contains(9) + if L.head != nil: n.next = L.head n.prev = L.head.prev @@ -244,11 +639,40 @@ proc append*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) = L.head = n proc append*[T](L: var DoublyLinkedRing[T], value: T) = - ## appends a value to `L`. Efficiency: O(1). + ## Appends (adds to the end) a value to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for appending a node + ## * `prepend proc <#prepend,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,DoublyLinkedRing[T],T>`_ for prepending a value + ## * `remove proc <#remove,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var a = initDoublyLinkedRing[int]() + a.append(9) + a.append(8) + assert a.contains(9) append(L, newDoublyLinkedNode(value)) proc prepend*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) = - ## prepends a node `n` to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a node `n` to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,DoublyLinkedRing[T],T>`_ for appending a value + ## * `prepend proc <#prepend,DoublyLinkedRing[T],T>`_ for prepending a value + ## * `remove proc <#remove,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var + a = initDoublyLinkedRing[int]() + n = newDoublyLinkedNode[int](9) + a.prepend(n) + assert a.contains(9) + if L.head != nil: n.next = L.head n.prev = L.head.prev @@ -260,11 +684,34 @@ proc prepend*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) = L.head = n proc prepend*[T](L: var DoublyLinkedRing[T], value: T) = - ## prepends a value to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a value to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,DoublyLinkedRing[T],T>`_ for appending a value + ## * `prepend proc <#prepend,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for prepending a node + ## * `remove proc <#remove,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var a = initDoublyLinkedRing[int]() + a.prepend(9) + a.prepend(8) + assert a.contains(9) prepend(L, newDoublyLinkedNode(value)) proc remove*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) = - ## removes `n` from `L`. Efficiency: O(1). + ## Removes `n` from `L`. Efficiency: O(1). + runnableExamples: + var + a = initDoublyLinkedRing[int]() + n = newDoublyLinkedNode[int](5) + a.append(n) + assert 5 in a + a.remove(n) + assert 5 notin a + n.next.prev = n.prev n.prev.next = n.next if n == L.head: -- cgit 1.4.1-2-gfad0 From 413755fd45f5a77f9c3323cf7185830249c3f310 Mon Sep 17 00:00:00 2001 From: alaviss Date: Mon, 21 Jan 2019 22:57:48 +0700 Subject: Correct lineinfo for accent quoted symbols in proc definition (#10399) * compiler/parser: preserve lineinfo for accent quoted symbols Previously the lineinfo for symbol $$$ in this example is: proc `$$$` ^ After this commit: proc `$$$` ^ * compiler/semstmts: correct lineinfo for accent quoted idents Previously nimsuggest would highlight this as: proc `$$$` ^~~ After this commit: proc `$$$` ^~~ * nimsuggest/tests: add a test for accent quoted proc Disabled by default --- compiler/parser.nim | 5 ++++- compiler/semstmts.nim | 20 ++++++++++---------- nimsuggest/tests/taccent_highlight.nim | 8 ++++++++ 3 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 nimsuggest/tests/taccent_highlight.nim diff --git a/compiler/parser.nim b/compiler/parser.nim index afc49c0d3..c9626c527 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -332,12 +332,15 @@ proc parseSymbol(p: var TParser, mode = smNormal): PNode = parMessage(p, errIdentifierExpected, p.tok) break of tkOpr, tkDot, tkDotDot, tkEquals, tkParLe..tkParDotRi: + let lineinfo = parLineinfo(p) var accm = "" while p.tok.tokType in {tkOpr, tkDot, tkDotDot, tkEquals, tkParLe..tkParDotRi}: accm.add(tokToStr(p.tok)) getTok(p) - result.add(newIdentNodeP(p.lex.cache.getIdent(accm), p)) + let node = newNodeI(nkIdent, lineinfo) + node.ident = p.lex.cache.getIdent(accm) + result.add(node) of tokKeywordLow..tokKeywordHigh, tkSymbol, tkIntLit..tkCharLit: result.add(newIdentNodeP(p.lex.cache.getIdent(tokToStr(p.tok)), p)) getTok(p) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index de39e95c8..288820d86 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -318,16 +318,16 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym = result = semIdentWithPragma(c, kind, n, {}) if result.owner.kind == skModule: incl(result.flags, sfGlobal) - let info = case n.kind - of nkPostfix: - n.sons[1].info - of nkPragmaExpr: - if n.sons[0].kind == nkPostfix: - n.sons[0].sons[1].info - else: - n.sons[0].info - else: - n.info + + proc getLineInfo(n: PNode): TLineInfo = + case n.kind + of nkPostfix: + getLineInfo(n.sons[1]) + of nkAccQuoted, nkPragmaExpr: + getLineInfo(n.sons[0]) + else: + n.info + let info = getLineInfo(n) suggestSym(c.config, info, result, c.graph.usageSym) proc checkNilable(c: PContext; v: PSym) = diff --git a/nimsuggest/tests/taccent_highlight.nim b/nimsuggest/tests/taccent_highlight.nim new file mode 100644 index 000000000..2f03d7c86 --- /dev/null +++ b/nimsuggest/tests/taccent_highlight.nim @@ -0,0 +1,8 @@ +proc `$$$`#[!]# + +discard """ +disabled:true +$nimsuggest --tester $file +>highlight $1 +highlight;;skProc;;1;;6;;3 +""" -- cgit 1.4.1-2-gfad0 From a4cdd25b19b0ec98826a01e1f57da1c2fb8920af Mon Sep 17 00:00:00 2001 From: Oscar Nihlgård Date: Mon, 21 Jan 2019 17:00:33 +0100 Subject: Support system.reset in vm (#10400) --- compiler/vm.nim | 2 -- compiler/vmdef.nim | 2 +- compiler/vmgen.nim | 4 +++- tests/vm/treset.nim | 28 ++++++++++++++++++++++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 tests/vm/treset.nim diff --git a/compiler/vm.nim b/compiler/vm.nim index 180f3800b..10d38fe77 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -1249,8 +1249,6 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = let newLen = regs[rb].intVal.int if regs[ra].node.isNil: stackTrace(c, tos, pc, errNilAccess) else: c.setLenSeq(regs[ra].node, newLen, c.debug[pc]) - of opcReset: - internalError(c.config, c.debug[pc], "too implement") of opcNarrowS: decodeB(rkInt) let min = -(1.BiggestInt shl (rb-1)) diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim index 493078f74..a43f8dbba 100644 --- a/compiler/vmdef.nim +++ b/compiler/vmdef.nim @@ -73,7 +73,7 @@ type opcContainsSet, opcRepr, opcSetLenStr, opcSetLenSeq, opcIsNil, opcOf, opcIs, opcSubStr, opcParseFloat, opcConv, opcCast, - opcQuit, opcReset, + opcQuit, opcNarrowS, opcNarrowU, opcAddStrCh, diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 033cc81f0..e7993dfb2 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1105,7 +1105,9 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = of mReset: unused(c, n, dest) var d = c.genx(n.sons[1]) - c.gABC(n, opcReset, d) + c.gABx(n, opcLdNull, d, c.genType(n.sons[1].typ)) + c.gABx(n, opcNodeToReg, d, d) + c.genAsgnPatch(n.sons[1], d) of mOf, mIs: if dest < 0: dest = c.getTemp(n.typ) var tmp = c.genx(n.sons[1]) diff --git a/tests/vm/treset.nim b/tests/vm/treset.nim new file mode 100644 index 000000000..56fe19b19 --- /dev/null +++ b/tests/vm/treset.nim @@ -0,0 +1,28 @@ +static: + type Obj = object + field: int + var o = Obj(field: 1) + reset(o) + doAssert o.field == 0 + +static: + var i = 2 + reset(i) + doAssert i == 0 + +static: + var i = new int + reset(i) + doAssert i.isNil + +static: + var s = @[1, 2, 3] + reset(s) + doAssert s == @[] + +static: + proc f() = + var i = 2 + reset(i) + doAssert i == 0 + f() \ No newline at end of file -- cgit 1.4.1-2-gfad0 From ae5d8fbd9d6b0c6469c2245da8a54d7b4c12f54f Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Mon, 21 Jan 2019 17:27:36 +0100 Subject: Proper check for tyStatic[T] -> U conversions (#10382) Drop the outer tyStatic shell then perform the check. Fixes #7609 --- compiler/semexprs.nim | 2 ++ tests/statictypes/tstatictypes.nim | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 5e1e4cbbd..82f948492 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -108,6 +108,8 @@ const proc checkConvertible(c: PContext, castDest, src: PType): TConvStatus = result = convOK + # We're interested in the inner type and not in the static tag + var src = src.skipTypes({tyStatic}) if sameType(castDest, src) and castDest.sym == src.sym: # don't annoy conversions that may be needed on another processor: if castDest.kind notin IntegralTypes+{tyRange}: diff --git a/tests/statictypes/tstatictypes.nim b/tests/statictypes/tstatictypes.nim index 9888165cc..b7cde6124 100644 --- a/tests/statictypes/tstatictypes.nim +++ b/tests/statictypes/tstatictypes.nim @@ -132,3 +132,8 @@ block: var x = foo(y, 10, 15, [1, 2, 3]) doAssert x == (20, 10, 15, 3) +# #7609 +block: + type + Coord[N: static[int]] = tuple[col, row: range[0'i8 .. (N.int8-1)]] + Point[N: static[int]] = range[0'i16 .. N.int16 * N.int16 - 1] -- cgit 1.4.1-2-gfad0 From ee89ba6bdb664fe4972f2917499cff1afdac0bab Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Mon, 21 Jan 2019 19:12:17 +0100 Subject: Fix subtype conversion w/ varargs arguments (#10402) The type matching is done on the `T` of the `varargs[T]` so the conversion must be performed to `T` and not to the whole type. This problem is only noticeable with the cpp backend since C doesn't give a damn shit about your fucking (wrong) types. Fixes #9845 --- compiler/sigmatch.nim | 5 +++-- tests/typerel/t4799.nim | 1 + tests/typerel/t4799_1.nim | 1 + tests/typerel/t4799_2.nim | 1 + tests/typerel/t4799_3.nim | 1 + 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 9aae254f3..e08559db6 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -2071,6 +2071,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, # constructor in a call: if result == nil and f.kind == tyVarargs: if f.n != nil: + # Forward to the varargs converter result = localConvMatch(c, m, f, a, arg) else: r = typeRel(m, base(f), a) @@ -2083,10 +2084,10 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, # bug #4799, varargs accepting subtype relation object elif r == isSubtype: inc(m.subtypeMatches) - if f.kind == tyTypeDesc: + if base(f).kind == tyTypeDesc: result = arg else: - result = implicitConv(nkHiddenSubConv, f, arg, m, c) + result = implicitConv(nkHiddenSubConv, base(f), arg, m, c) m.baseTypeMatch = true else: result = userConvMatch(c, m, base(f), a, arg) diff --git a/tests/typerel/t4799.nim b/tests/typerel/t4799.nim index 075893476..814ad361d 100644 --- a/tests/typerel/t4799.nim +++ b/tests/typerel/t4799.nim @@ -1,4 +1,5 @@ discard """ + targets: "c cpp" output: "OK" """ diff --git a/tests/typerel/t4799_1.nim b/tests/typerel/t4799_1.nim index 549b6bf3c..e66aa1a9a 100644 --- a/tests/typerel/t4799_1.nim +++ b/tests/typerel/t4799_1.nim @@ -1,4 +1,5 @@ discard """ + targets: "c cpp" outputsub: '''ObjectAssignmentError''' exitcode: "1" """ diff --git a/tests/typerel/t4799_2.nim b/tests/typerel/t4799_2.nim index cfd399a6e..ff20c2426 100644 --- a/tests/typerel/t4799_2.nim +++ b/tests/typerel/t4799_2.nim @@ -1,4 +1,5 @@ discard """ + targets: "c cpp" outputsub: '''ObjectAssignmentError''' exitcode: "1" """ diff --git a/tests/typerel/t4799_3.nim b/tests/typerel/t4799_3.nim index 784eee8fc..4a8a158dd 100644 --- a/tests/typerel/t4799_3.nim +++ b/tests/typerel/t4799_3.nim @@ -1,4 +1,5 @@ discard """ + targets: "c cpp" outputsub: '''ObjectAssignmentError''' exitcode: "1" """ -- cgit 1.4.1-2-gfad0