diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2018-07-18 09:46:30 +0200 |
---|---|---|
committer | Andreas Rumpf <rumpf_a@web.de> | 2018-07-18 09:46:30 +0200 |
commit | 4389409e2657e4cec098180299b2d41b1a3a40f7 (patch) | |
tree | bdeb765d6687ec0aa175d6cbef3458b18b76b1d1 | |
parent | 32afdc09c6e2e6b32566df9e70cb71ae43eb9355 (diff) | |
parent | 6eedac3207cad9f7b4bfe8d631a9373a91b85846 (diff) | |
download | Nim-4389409e2657e4cec098180299b2d41b1a3a40f7.tar.gz |
fix merge conflict
65 files changed, 2188 insertions, 918 deletions
diff --git a/bin/nim-gdb b/bin/nim-gdb new file mode 100755 index 000000000..e7b41094d --- /dev/null +++ b/bin/nim-gdb @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Exit if anything fails +set -e + +# Find out where the pretty printer Python module is +NIM_SYSROOT=$(dirname $(dirname $(readlink -e $(which nim)))) +GDB_PYTHON_MODULE_PATH="$NIM_SYSROOT/tools/nim-gdb.py" + +# Run GDB with the additional arguments that load the pretty printers +# Set the environment variable `NIM_GDB` to overwrite the call to a +# different/specific command (defaults to `gdb`). +NIM_GDB="${NIM_GDB:-gdb}" +# exec replaces the new process of bash with gdb. It is always good to +# have fewer processes. +exec ${NIM_GDB} \ + -eval-command "source $GDB_PYTHON_MODULE_PATH" \ + "$@" diff --git a/changelog.md b/changelog.md index 0943d7836..43b351ef1 100644 --- a/changelog.md +++ b/changelog.md @@ -50,9 +50,15 @@ - For string inputs, ``strutils.isUpperAscii`` and ``strutils.isLowerAscii`` now require a second mandatory parameter ``skipNonAlpha``. +- ``osLastError`` is now marked with ``sideEffect`` - The procs ``parseHexInt`` and ``parseOctInt`` now fail on empty strings and strings containing only valid prefixes, e.g. "0x" for hex integers. +- ``terminal.setCursorPos`` and ``terminal.setCursorXPos`` now work correctly + with 0-based coordinates on POSIX (previously, you needed to use + 1-based coordinates on POSIX for correct behaviour; the Windows behaviour + was always correct). + #### Breaking changes in the compiler diff --git a/compiler/ast.nim b/compiler/ast.nim index 7cc785ad7..279ed5a08 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -322,7 +322,8 @@ const usesEffects* = 1 # read effects at position 1 writeEffects* = 2 # write effects at position 2 tagEffects* = 3 # user defined tag ('gc', 'time' etc.) - effectListLen* = 4 # list of effects list + pragmasEffects* = 4 # not an effect, but a slot for pragmas in proc type + effectListLen* = 5 # list of effects list type TTypeKind* = enum # order is important! diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index f474ca65e..333376f6a 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -27,9 +27,9 @@ proc lineInfoToStr*(conf: ConfigRef; info: TLineInfo): Rope when declared(echo): # these are for debugging only: They are not really deprecated, but I want # the warning so that release versions do not contain debugging statements: - proc debug*(n: PSym; conf: ConfigRef = nil) {.deprecated.} - proc debug*(n: PType; conf: ConfigRef = nil) {.deprecated.} - proc debug*(n: PNode; conf: ConfigRef = nil) {.deprecated.} + proc debug*(n: PSym; conf: ConfigRef = nil) {.exportc: "debugSym", deprecated.} + proc debug*(n: PType; conf: ConfigRef = nil) {.exportc: "debugType", deprecated.} + proc debug*(n: PNode; conf: ConfigRef = nil) {.exportc: "debugNode", deprecated.} template debug*(x: PSym|PType|PNode) {.deprecated.} = when compiles(c.config): diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 0d87cc19d..d25a4ad3d 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -255,16 +255,14 @@ proc genGenericAsgn(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = # here for this flag, where it is reasonably safe to do so # (for objects, etc.): if p.config.selectedGC == gcDestructors: - useStringh(p.module) linefmt(p, cpsStmts, - "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", - addrLoc(p.config, dest), addrLoc(p.config, src), rdLoc(dest)) + "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", + addrLoc(p.config, dest), addrLoc(p.config, src), rdLoc(dest)) elif needToCopy notin flags or tfShallow in skipTypes(dest.t, abstractVarRange).flags: if dest.storage == OnStack or not usesWriteBarrier(p.config): - useStringh(p.module) linefmt(p, cpsStmts, - "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", + "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", addrLoc(p.config, dest), addrLoc(p.config, src), rdLoc(dest)) else: linefmt(p, cpsStmts, "#genericShallowAssign((void*)$1, (void*)$2, $3);$n", @@ -345,9 +343,8 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = if needsComplexAssignment(dest.t): genGenericAsgn(p, dest, src, flags) else: - useStringh(p.module) linefmt(p, cpsStmts, - "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", + "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", rdLoc(dest), rdLoc(src), getTypeDesc(p.module, dest.t)) of tyOpenArray, tyVarargs: # open arrays are always on the stack - really? What if a sequence is @@ -358,16 +355,14 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = addrLoc(p.config, dest), addrLoc(p.config, src), genTypeInfo(p.module, dest.t, dest.lode.info)) else: - useStringh(p.module) linefmt(p, cpsStmts, - # bug #4799, keep the memcpy for a while - #"memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($1[0])*$1Len_0);$n", + # bug #4799, keep the nimCopyMem for a while + #"#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($1[0])*$1Len_0);$n", "$1 = $2;$n", rdLoc(dest), rdLoc(src)) of tySet: if mapType(p.config, ty) == ctArray: - useStringh(p.module) - linefmt(p, cpsStmts, "memcpy((void*)$1, (NIM_CONST void*)$2, $3);$n", + linefmt(p, cpsStmts, "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, $3);$n", rdLoc(dest), rdLoc(src), rope(getSize(p.config, dest.t))) else: linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) @@ -412,8 +407,7 @@ proc genDeepCopy(p: BProc; dest, src: TLoc) = genTypeInfo(p.module, dest.t, dest.lode.info)) of tySet: if mapType(p.config, ty) == ctArray: - useStringh(p.module) - linefmt(p, cpsStmts, "memcpy((void*)$1, (NIM_CONST void*)$2, $3);$n", + linefmt(p, cpsStmts, "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, $3);$n", rdLoc(dest), rdLoc(src), rope(getSize(p.config, dest.t))) else: linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) @@ -1515,9 +1509,8 @@ proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) = if op == mHigh: unaryExpr(p, e, d, "($1Len_0-1)") else: unaryExpr(p, e, d, "$1Len_0") of tyCString: - useStringh(p.module) - if op == mHigh: unaryExpr(p, e, d, "($1 ? (strlen($1)-1) : -1)") - else: unaryExpr(p, e, d, "($1 ? strlen($1) : 0)") + if op == mHigh: unaryExpr(p, e, d, "($1 ? (#nimCStrLen($1)-1) : -1)") + else: unaryExpr(p, e, d, "($1 ? #nimCStrLen($1) : 0)") of tyString: if not p.module.compileToCpp: if op == mHigh: unaryExpr(p, e, d, "($1 ? ($1->Sup.len-1) : -1)") @@ -1672,7 +1665,7 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) = " $3 = (($4[$1] & ~ $5[$1]) == 0);$n" & " if (!$3) break;}$n", "for ($1 = 0; $1 < $2; $1++) { $n" & " $3 = (($4[$1] & ~ $5[$1]) == 0);$n" & " if (!$3) break;}$n" & - "if ($3) $3 = (memcmp($4, $5, $2) != 0);$n", + "if ($3) $3 = (#nimCmpMem($4, $5, $2) != 0);$n", "&", "|", "& ~", "^"] var a, b, i: TLoc var setType = skipTypes(e.sons[1].typ, abstractVar) @@ -1711,11 +1704,10 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) = initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) if d.k == locNone: getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyBool), d) - lineF(p, cpsStmts, lookupOpr[op], + linefmt(p, cpsStmts, lookupOpr[op], [rdLoc(i), rope(size), rdLoc(d), rdLoc(a), rdLoc(b)]) of mEqSet: - useStringh(p.module) - binaryExprChar(p, e, d, "(memcmp($1, $2, " & $(size) & ")==0)") + binaryExprChar(p, e, d, "(#nimCmpMem($1, $2, " & $(size) & ")==0)") of mMulSet, mPlusSet, mMinusSet, mSymDiffSet: # we inline the simple for loop for better code generation: getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyInt), i) # our counter @@ -1979,7 +1971,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = proc genSetConstr(p: BProc, e: PNode, d: var TLoc) = # example: { a..b, c, d, e, f..g } # we have to emit an expression of the form: - # memset(tmp, 0, sizeof(tmp)); inclRange(tmp, a, b); incl(tmp, c); + # nimZeroMem(tmp, sizeof(tmp)); inclRange(tmp, a, b); incl(tmp, c); # incl(tmp, d); incl(tmp, e); inclRange(tmp, f, g); var a, b, idx: TLoc @@ -1989,8 +1981,7 @@ proc genSetConstr(p: BProc, e: PNode, d: var TLoc) = if d.k == locNone: getTemp(p, e.typ, d) if getSize(p.config, e.typ) > 8: # big set: - useStringh(p.module) - lineF(p, cpsStmts, "memset($1, 0, sizeof($2));$n", + linefmt(p, cpsStmts, "#nimZeroMem($1, sizeof($2));$n", [rdLoc(d), getTypeDesc(p.module, e.typ)]) for it in e.sons: if it.kind == nkRange: diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 8a0e07686..9a8d3bcd3 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -1146,5 +1146,9 @@ proc genAsgn(p: BProc, e: PNode, fastAsgn: bool) = proc genStmts(p: BProc, t: PNode) = var a: TLoc + + let isPush = hintExtendedContext in p.config.notes + if isPush: pushInfoContext(p.config, t.info) expr(p, t, a) + if isPush: popInfoContext(p.config) internalAssert p.config, a.k in {locNone, locTemp, locLocalVar} diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index ea06544bb..b83c96660 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -48,7 +48,7 @@ proc mangleName(m: BModule; s: PSym): Rope = result = s.loc.r if result == nil: result = s.name.s.mangle.rope - add(result, idOrSig(s, m.module.name.s, m.sigConflicts)) + add(result, idOrSig(s, m.module.name.s.mangle, m.sigConflicts)) s.loc.r = result writeMangledName(m.ndi, s, m.config) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 180aa4731..8d8fdfcd9 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -80,11 +80,6 @@ proc isSimpleConst(typ: PType): bool = {tyTuple, tyObject, tyArray, tySet, tySequence} and not (t.kind == tyProc and t.callConv == ccClosure) -proc useStringh(m: BModule) = - if includesStringh notin m.flags: - incl m.flags, includesStringh - m.includeHeader("<string.h>") - proc useHeader(m: BModule, sym: PSym) = if lfHeader in sym.loc.flags: assert(sym.annex != nil) @@ -329,10 +324,9 @@ proc resetLoc(p: BProc, loc: var TLoc) = # field, so disabling this should be safe: genObjectInit(p, cpsStmts, loc.t, loc, true) else: - useStringh(p.module) # array passed as argument decayed into pointer, bug #7332 # so we use getTypeDesc here rather than rdLoc(loc) - linefmt(p, cpsStmts, "memset((void*)$1, 0, sizeof($2));$n", + linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n", addrLoc(p.config, loc), getTypeDesc(p.module, loc.t)) # XXX: We can be extra clever here and call memset only # on the bytes following the m_type field? @@ -345,11 +339,10 @@ proc constructLoc(p: BProc, loc: TLoc, isTemp = false) = getTypeDesc(p.module, typ)) else: if not isTemp or containsGarbageCollectedRef(loc.t): - # don't use memset for temporary values for performance if we can + # don't use nimZeroMem for temporary values for performance if we can # avoid it: if not isImportedCppType(typ): - useStringh(p.module) - linefmt(p, cpsStmts, "memset((void*)$1, 0, sizeof($2));$n", + linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n", addrLoc(p.config, loc), getTypeDesc(p.module, typ)) genObjectInit(p, cpsStmts, loc.t, loc, true) @@ -680,6 +673,7 @@ proc generateHeaders(m: BModule) = add(m.s[cfsHeaders], "#undef linux\L") add(m.s[cfsHeaders], "#undef mips\L") add(m.s[cfsHeaders], "#undef near\L") + add(m.s[cfsHeaders], "#undef far\L") add(m.s[cfsHeaders], "#undef powerpc\L") add(m.s[cfsHeaders], "#undef unix\L") diff --git a/compiler/docgen.nim b/compiler/docgen.nim index db4e301d4..75b599ae9 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -16,7 +16,7 @@ import wordrecg, syntaxes, renderer, lexer, packages/docutils/rstast, packages/docutils/rst, packages/docutils/rstgen, times, packages/docutils/highlite, sempass2, json, xmltree, cgi, - typesrenderer, astalgo, modulepaths, lineinfos + typesrenderer, astalgo, modulepaths, lineinfos, sequtils type TSections = array[TSymKind, Rope] @@ -259,11 +259,19 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe of tkSpaces, tkInvalid: add(result, literal) of tkCurlyDotLe: - dispA(d.conf, result, """<span class="Other pragmabegin">$1</span><div class="pragma">""", + dispA(d.conf, result, "<span>" & # This span is required for the JS to work properly + """<span class="Other">{</span><span class="Other pragmadots">...</span><span class="Other">}</span> +</span> +<span class="pragmawrap"> +<span class="Other">$1</span> +<span class="pragma">""".replace("\n", ""), # Must remove newlines because wrapped in a <pre> "\\spanOther{$1}", [rope(esc(d.target, literal))]) of tkCurlyDotRi: - dispA(d.conf, result, "</div><span class=\"Other pragmaend\">$1</span>", + dispA(d.conf, result, """ +</span> +<span class="Other">$1</span> +</span>""".replace("\n", ""), "\\spanOther{$1}", [rope(esc(d.target, literal))]) of tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi, @@ -280,7 +288,7 @@ proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) = of nkCallKinds: if n[0].kind == nkSym and n[0].sym.magic == mRunnableExamples and n.len >= 2 and n.lastSon.kind == nkStmtList: - dispA(d.conf, dest, "\n<strong class=\"examples_text\">$1</strong>\n", + dispA(d.conf, dest, "\n<p><strong class=\"examples_text\">$1</strong></p>\n", "\n\\textbf{$1}\n", [rope"Examples:"]) inc d.listingCounter let id = $d.listingCounter diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index a48067abb..575e30a79 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -434,10 +434,7 @@ proc completeCFilePath*(conf: ConfigRef; cfile: string, createSubDir: bool = tru proc toObjFile*(conf: ConfigRef; filename: string): string = # Object file for compilation - #if filename.endsWith(".cpp"): - # result = changeFileExt(filename, "cpp." & CC[cCompiler].objExt) - #else: - result = changeFileExt(filename, CC[conf.cCompiler].objExt) + result = filename & "." & CC[conf.cCompiler].objExt proc addFileToCompile*(conf: ConfigRef; cf: Cfile) = conf.toCompile.add(cf) diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index cad1fe6aa..b8678e6ba 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -45,7 +45,8 @@ type hintExecuting, hintLinking, hintDependency, hintSource, hintPerformance, hintStackTrace, hintGCStats, hintGlobalVar, - hintUser, hintUserRaw + hintUser, hintUserRaw, + hintExtendedContext const MsgKindToStr*: array[TMsgKind, string] = [ @@ -116,7 +117,9 @@ const hintGCStats: "$1", hintGlobalVar: "global variable declared here", hintUser: "$1", - hintUserRaw: "$1"] + hintUserRaw: "$1", + hintExtendedContext: "$1", + ] const WarningsToStr* = ["CannotOpenFile", "OctalEscape", @@ -132,12 +135,14 @@ const "GcMem", "Destructor", "LockLevel", "ResultShadowed", "Spacing", "User"] - HintsToStr* = ["Success", "SuccessX", "LineTooLong", + HintsToStr* = [ + "Success", "SuccessX", "LineTooLong", "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", "ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf", "Path", "CondTrue", "Name", "Pattern", "Exec", "Link", "Dependency", "Source", "Performance", "StackTrace", "GCStats", "GlobalVar", - "User", "UserRaw"] + "User", "UserRaw", "ExtendedContext", + ] const fatalMin* = errUnknown @@ -157,30 +162,17 @@ type TNoteKind* = range[warnMin..hintMax] # "notes" are warnings or hints TNoteKinds* = set[TNoteKind] -const - NotesVerbosity*: array[0..3, TNoteKinds] = [ - {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent, warnUninit, - warnProveField, warnProveIndex, - warnGcUnsafe, - hintSuccessX, hintPath, hintConf, - hintProcessing, hintPattern, - hintDependency, - hintExecuting, hintLinking, - hintCodeBegin, hintCodeEnd, - hintSource, hintStackTrace, - hintGlobalVar, hintGCStats}, - {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent, warnUninit, - warnProveField, warnProveIndex, - warnGcUnsafe, - hintPath, - hintDependency, - hintCodeBegin, hintCodeEnd, - hintSource, hintStackTrace, - hintGlobalVar, hintGCStats}, - {low(TNoteKind)..high(TNoteKind)} - {hintStackTrace, warnUninit}, - {low(TNoteKind)..high(TNoteKind)}] +proc computeNotesVerbosity(): array[0..3, TNoteKinds] = + result[3] = {low(TNoteKind)..high(TNoteKind)} - {} + result[2] = result[3] - {hintStackTrace, warnUninit, hintExtendedContext} + result[1] = result[2] - {warnShadowIdent, warnProveField, warnProveIndex, + warnGcUnsafe, hintPath, hintDependency, hintCodeBegin, hintCodeEnd, + hintSource, hintGlobalVar, hintGCStats} + result[0] = result[1] - {hintSuccessX, hintConf, hintProcessing, + hintPattern, hintExecuting, hintLinking} const + NotesVerbosity* = computeNotesVerbosity() errXMustBeCompileTime* = "'$1' can only be used in compile-time context" errArgsNeedRunOption* = "arguments can only be given if the '--run' option is selected" diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 3ce2e157d..83cf288ff 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -189,7 +189,7 @@ proc putComment(g: var TSrcGen, s: string) = put(g, tkComment, com) com = "## " inc(i) - if i < s.len and s[i] == '\x0A': inc(i) + if i <= hi and s[i] == '\x0A': inc(i) optNL(g, ind) of '\x0A': put(g, tkComment, com) @@ -226,7 +226,7 @@ proc maxLineLength(s: string): int = break of '\x0D': inc(i) - if s[i] == '\x0A': inc(i) + if i <= hi and s[i] == '\x0A': inc(i) result = max(result, lineLen) lineLen = 0 of '\x0A': @@ -247,7 +247,7 @@ proc putRawStr(g: var TSrcGen, kind: TTokType, s: string) = put(g, kind, str) str = "" inc(i) - if (i <= hi) and (s[i] == '\x0A'): inc(i) + if i <= hi and s[i] == '\x0A': inc(i) optNL(g, 0) of '\x0A': put(g, kind, str) @@ -416,7 +416,7 @@ proc lsub(g: TSrcGen; n: PNode): int = of nkCast: result = lsub(g, n.sons[0]) + lsub(g, n.sons[1]) + len("cast[]()") of nkAddr: result = (if n.len>0: lsub(g, n.sons[0]) + len("addr()") else: 4) of nkStaticExpr: result = lsub(g, n.sons[0]) + len("static_") - of nkHiddenAddr, nkHiddenDeref: result = lsub(g, n.sons[0]) + of nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString: result = lsub(g, n.sons[0]) of nkCommand: result = lsub(g, n.sons[0]) + lcomma(g, n, 1) + 1 of nkExprEqExpr, nkAsgn, nkFastAsgn: result = lsons(g, n) + 3 of nkPar, nkCurly, nkBracket, nkClosure: result = lcomma(g, n) + 2 @@ -446,7 +446,7 @@ proc lsub(g: TSrcGen; n: PNode): int = of nkChckRangeF: result = len("chckRangeF") + 2 + lcomma(g, n) of nkChckRange64: result = len("chckRange64") + 2 + lcomma(g, n) of nkChckRange: result = len("chckRange") + 2 + lcomma(g, n) - of nkObjDownConv, nkObjUpConv, nkStringToCString, nkCStringToString: + of nkObjDownConv, nkObjUpConv: result = 2 if sonsLen(n) >= 1: result = result + lsub(g, n.sons[0]) result = result + lcomma(g, n, 1) @@ -968,7 +968,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = put(g, tkParLe, "(") gcomma(g, n) put(g, tkParRi, ")") - of nkObjDownConv, nkObjUpConv, nkStringToCString, nkCStringToString: + of nkObjDownConv, nkObjUpConv: if sonsLen(n) >= 1: gsub(g, n.sons[0]) put(g, tkParLe, "(") gcomma(g, n, 1) @@ -1020,7 +1020,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = of nkBind: putWithSpace(g, tkBind, "bind") gsub(g, n, 0) - of nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref: + of nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString: gsub(g, n, 0) of nkLambda: putWithSpace(g, tkProc, "proc") @@ -1073,9 +1073,13 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = elif n[0].kind == nkSym: n[0].sym.name elif n[0].kind in {nkOpenSymChoice, nkClosedSymChoice}: n[0][0].sym.name else: nil - if n[1].kind == nkPrefix or (opr != nil and renderer.isKeyword(opr)): + var n_next = n[1] + while n_next.kind in {nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, + nkStringToCString, nkCStringToString} and n_next.len > 0: + n_next = n_next[0] + if n_next.kind == nkPrefix or (opr != nil and renderer.isKeyword(opr)): put(g, tkSpaces, Space) - if n.sons[1].kind == nkInfix: + if n_next.kind == nkInfix: put(g, tkParLe, "(") gsub(g, n.sons[1]) put(g, tkParRi, ")") diff --git a/compiler/sem.nim b/compiler/sem.nim index b242e4db6..0d484d276 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -610,14 +610,21 @@ proc myProcess(context: PPassContext, n: PNode): PNode = proc testExamples(c: PContext) = let inp = toFullPath(c.config, c.module.info) let outp = inp.changeFileExt"" & "_examples.nim" + let nimcache = outp.changeFileExt"" & "_nimcache" renderModule(c.runnableExamples, inp, outp) let backend = if isDefined(c.config, "js"): "js" elif isDefined(c.config, "cpp"): "cpp" elif isDefined(c.config, "objc"): "objc" else: "c" - if os.execShellCmd(os.getAppFilename() & " " & backend & " -r " & outp) != 0: + if os.execShellCmd(os.getAppFilename() & " " & backend & " --nimcache:" & nimcache & " -r " & outp) != 0: quit "[Examples] failed" - removeFile(outp) + else: + removeFile(outp) + removeFile(outp.changeFileExt(ExeExt)) + try: + removeDir(nimcache) + except OSError: + discard proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode = var c = PContext(context) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 55cf05094..3a72d1f5a 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -907,8 +907,11 @@ proc buildEchoStmt(c: PContext, n: PNode): PNode = result = semExpr(c, result) 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) + if isPush: popInfoContext(c.config) proc isTypeExpr(n: PNode): bool = case n.kind diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 1975fb77b..b5875c67a 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -171,7 +171,7 @@ proc semTypeTraits(c: PContext, n: PNode): PNode = proc semOrd(c: PContext, n: PNode): PNode = result = n let parType = n.sons[1].typ - if isOrdinalType(parType): + if isOrdinalType(parType, allowEnumWithHoles=true): discard elif parType.kind == tySet: result.typ = makeRangeType(c, firstOrd(c.config, parType), lastOrd(c.config, parType), n.info) diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 4d3ee0408..bdea07ea8 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -552,6 +552,10 @@ proc isOwnedProcVar(n: PNode; owner: PSym): bool = # XXX prove the soundness of this effect system rule result = n.kind == nkSym and n.sym.kind == skParam and owner == n.sym.owner +proc isNoEffectList(n: PNode): bool {.inline.} = + assert n.kind == nkEffectList + n.len == 0 or (n[tagEffects] == nil and n[exceptionEffects] == nil) + proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = let a = skipConvAndClosure(n) let op = a.typ @@ -561,7 +565,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = let s = n.skipConv if s.kind == nkSym and s.sym.kind in routineKinds: propagateEffects(tracked, n, s.sym) - elif effectList.len == 0: + elif isNoEffectList(effectList): if isForwardedProc(n): # we have no explicit effects but it's a forward declaration and so it's # stated there are no additional effects, so simply propagate them: @@ -723,7 +727,7 @@ proc track(tracked: PEffects, n: PNode) = var effectList = op.n.sons[0] if a.kind == nkSym and a.sym.kind == skMethod: propagateEffects(tracked, n, a.sym) - elif effectList.len == 0: + elif isNoEffectList(effectList): if isForwardedProc(a): propagateEffects(tracked, n, a.sym) elif isIndirectCall(a, tracked.owner): @@ -781,6 +785,15 @@ proc track(tracked: PEffects, n: PNode) = initVar(tracked, child.sons[i], volatileCheck=false) addAsgnFact(tracked.guards, child.sons[i], last) notNilCheck(tracked, last, child.sons[i].typ) + elif child.kind == nkVarTuple and last.kind != nkEmpty: + for i in 0 .. child.len-2: + if child[i].kind == nkEmpty or + child[i].kind == nkSym and child[i].sym.name.s == "_": + continue + initVar(tracked, child[i], volatileCheck=false) + if last.kind in {nkPar, nkTupleConstr}: + addAsgnFact(tracked.guards, child[i], last[i]) + notNilCheck(tracked, last[i], child[i].typ) # since 'var (a, b): T = ()' is not even allowed, there is always type # inference for (a, b) and thus no nil checking is necessary. of nkConstSection: @@ -897,19 +910,18 @@ proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) = [$branch.typ.lockLevel, $disp.typ.lockLevel]) proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) = - var effects = t.n.sons[0] + var effects = t.n[0] if t.kind != tyProc or effects.kind != nkEffectList: return - - let - raisesSpec = effectSpec(n, wRaises) - tagsSpec = effectSpec(n, wTags) - if not isNil(raisesSpec) or not isNil(tagsSpec): + if n.kind != nkEmpty: internalAssert g.config, effects.len == 0 newSeq(effects.sons, effectListLen) + let raisesSpec = effectSpec(n, wRaises) if not isNil(raisesSpec): - effects.sons[exceptionEffects] = raisesSpec + effects[exceptionEffects] = raisesSpec + let tagsSpec = effectSpec(n, wTags) if not isNil(tagsSpec): - effects.sons[tagEffects] = tagsSpec + effects[tagEffects] = tagsSpec + effects[pragmasEffects] = n proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects) = newSeq(effects.sons, effectListLen) @@ -917,6 +929,7 @@ proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects) = effects.sons[tagEffects] = newNodeI(nkArgList, s.info) effects.sons[usesEffects] = g.emptyNode effects.sons[writeEffects] = g.emptyNode + effects.sons[pragmasEffects] = g.emptyNode t.exc = effects.sons[exceptionEffects] t.tags = effects.sons[tagEffects] diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index e888b7f7c..2d5d47c6f 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -557,7 +557,10 @@ proc semCaseBranch(c: PContext, t, branch: PNode, branchIndex: int, return elif r.kind notin {nkCurly, nkBracket} or len(r) == 0: checkMinSonsLen(t, 1, c.config) - branch.sons[i] = skipConv(fitNode(c, t.sons[0].typ, r, r.info)) + var tmp = fitNode(c, t.sons[0].typ, r, r.info) + # the call to fitNode may introduce a call to a converter + if tmp.kind in {nkHiddenCallConv}: tmp = semConstExpr(c, tmp) + branch.sons[i] = skipConv(tmp) inc(covered) else: if r.kind == nkCurly: @@ -1341,6 +1344,10 @@ proc semTypeClass(c: PContext, n: PNode, prev: PType): PType = dummyName = param dummyType = candidateTypeSlot + # this can be true for 'nim check' on incomplete concepts, + # see bug #8230 + if dummyName.kind == nkEmpty: continue + internalAssert c.config, dummyName.kind == nkIdent var dummyParam = newSym(if modifier == tyTypeDesc: skType else: skVar, dummyName.ident, owner, param.info) diff --git a/compiler/sighashes.nim b/compiler/sighashes.nim index 0bf2b8459..8f95175e5 100644 --- a/compiler/sighashes.nim +++ b/compiler/sighashes.nim @@ -163,7 +163,7 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) = c.hashType t.sons[i], flags else: c.hashType t.lastSon, flags - of tyAlias, tySink, tyUserTypeClasses: + of tyAlias, tySink, tyUserTypeClasses, tyInferred: c.hashType t.lastSon, flags of tyBool, tyChar, tyInt..tyUInt64: # no canonicalization for integral types, so that e.g. ``pid_t`` is diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index f9d1edc89..29f16b808 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -569,7 +569,10 @@ proc procParamTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = # signature. There is a change that the target # type is already fully-determined, so we are # going to try resolve it - f = generateTypeInstance(c.c, c.bindings, c.call.info, f) + if c.call != nil: + f = generateTypeInstance(c.c, c.bindings, c.call.info, f) + else: + f = nil if f == nil or f.isMetaType: # no luck resolving the type, so the inference fails return isBothMetaConvertible @@ -637,7 +640,7 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = else: discard proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} = - template checkRange[T](a0, a1, f0, f1: T): TTypeRelation = + template checkRange[T](a0, a1, f0, f1: T): TTypeRelation = if a0 == f0 and a1 == f1: isEqual elif a0 >= f0 and a1 <= f1: @@ -647,12 +650,12 @@ proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} = isConvertible else: isNone - - if f.isOrdinalType: + + if f.isOrdinalType: checkRange(firstOrd(nil, a), lastOrd(nil, a), firstOrd(nil, f), lastOrd(nil, f)) - else: + else: checkRange(firstFloat(a), lastFloat(a), firstFloat(f), lastFloat(f)) - + proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = var @@ -1801,7 +1804,7 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType, # 'f <- dest' in order to not break the unification: # see tests/tgenericconverter: let srca = typeRel(m, src, a) - if srca notin {isEqual, isGeneric}: continue + if srca notin {isEqual, isGeneric, isSubtype}: continue let destIsGeneric = containsGenericType(dest) if destIsGeneric: @@ -1814,7 +1817,12 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType, s.info = arg.info result = newNodeIT(nkHiddenCallConv, arg.info, dest) addSon(result, s) - addSon(result, copyTree(arg)) + var param: PNode = nil + if srca == isSubtype: + param = implicitConv(nkHiddenSubConv, src, copyTree(arg), m, c) + else: + param = copyTree(arg) + addSon(result, param) inc(m.convMatches) m.genericConverter = srca == isGeneric or destIsGeneric return result @@ -2231,6 +2239,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m.state = csNoMatch return m.baseTypeMatch = false + m.typedescMatched = false n.sons[a].sons[1] = prepareOperand(c, formal.typ, n.sons[a].sons[1]) n.sons[a].typ = n.sons[a].sons[1].typ var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, @@ -2266,6 +2275,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, # beware of the side-effects in 'prepareOperand'! So only do it for # varargs matching. See tests/metatype/tstatic_overloading. m.baseTypeMatch = false + m.typedescMatched = false incl(marker, formal.position) n.sons[a] = prepareOperand(c, formal.typ, n.sons[a]) var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, @@ -2300,6 +2310,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, addSon(container, n.sons[a]) else: m.baseTypeMatch = false + m.typedescMatched = false n.sons[a] = prepareOperand(c, formal.typ, n.sons[a]) var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, n.sons[a], nOrig.sons[a]) diff --git a/compiler/types.nim b/compiler/types.nim index 76d233e8a..60a596a9a 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -133,18 +133,18 @@ proc elemType*(t: PType): PType = else: result = t.lastSon assert(result != nil) -proc isOrdinalType*(t: PType): bool = +proc enumHasHoles*(t: PType): bool = + var b = t.skipTypes({tyRange, tyGenericInst, tyAlias, tySink}) + result = b.kind == tyEnum and tfEnumHasHoles in b.flags + +proc isOrdinalType*(t: PType, allowEnumWithHoles = false): bool = assert(t != nil) const # caution: uint, uint64 are no ordinal types! baseKinds = {tyChar,tyInt..tyInt64,tyUInt8..tyUInt32,tyBool,tyEnum} parentKinds = {tyRange, tyOrdinal, tyGenericInst, tyAlias, tySink, tyDistinct} - t.kind in baseKinds or (t.kind in parentKinds and isOrdinalType(t.sons[0])) - -proc enumHasHoles*(t: PType): bool = - var b = t - while b.kind in {tyRange, tyGenericInst, tyAlias, tySink}: b = b.sons[0] - result = b.kind == tyEnum and tfEnumHasHoles in b.flags + (t.kind in baseKinds and not (t.enumHasHoles and not allowEnumWithHoles)) or + (t.kind in parentKinds and isOrdinalType(t.lastSon)) proc iterOverTypeAux(marker: var IntSet, t: PType, iter: TTypeIter, closure: RootRef): bool diff --git a/compiler/vm.nim b/compiler/vm.nim index 7f0dd80ed..373a64e39 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -533,8 +533,10 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = let s = regs[rb].node.strVal if s.isNil: stackTrace(c, tos, pc, errNilAccess) - elif idx <=% s.len: + elif idx <% s.len: regs[ra].intVal = s[idx].ord + elif idx == s.len and optLaxStrings in c.config.options: + regs[ra].intVal = 0 else: stackTrace(c, tos, pc, errIndexOutOfBounds) of opcWrArr: diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index 865ecd36e..bf2418eaf 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -229,7 +229,8 @@ proc mapTypeToAstX(cache: IdentCache; t: PType; info: TLineInfo; for i in 1..<t.sons.len: fp.add newIdentDefs(t.n[i], t.sons[i]) result.add fp - result.add newNodeI(nkEmpty, info) # pragmas aren't reconstructed yet + result.add if t.n[0].len > 0: t.n[0][pragmasEffects].copyTree + else: newNodeI(nkEmpty, info) else: result = mapTypeToBracket("proc", mNone, t, info) of tyOpenArray: result = mapTypeToBracket("openArray", mOpenArray, t, info) diff --git a/config/nim.cfg b/config/nim.cfg index 626f4494a..c30190a18 100644 --- a/config/nim.cfg +++ b/config/nim.cfg @@ -111,10 +111,10 @@ path="$lib/pure" @if nintendoswitch: cc = "switch_gcc" - switch_gcc.options.linker = "-g -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE $SWITCH_LIBS" - switch_gcc.cpp.options.linker = "-g -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE $SWITCH_LIBS" - switch_gcc.options.always = "-g -Wall -O2 -ffunction-sections -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE $SWITCH_INCLUDES -D__SWITCH__" - switch_gcc.cpp.options.always = "-g -Wall -O2 -ffunction-sections -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE $SWITCH_INCLUDES -D__SWITCH__ -fno-rtti -fno-exceptions -std=gnu++11" + switch_gcc.options.linker = "-g -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE" + switch_gcc.cpp.options.linker = "-g -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE" + switch_gcc.options.always = "-g -Wall -O2 -ffunction-sections -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE -D__SWITCH__" + switch_gcc.cpp.options.always = "-g -Wall -O2 -ffunction-sections -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE -D__SWITCH__ -fno-rtti -fno-exceptions -std=gnu++11" @end # Configuration for the Intel C/C++ compiler: diff --git a/config/nimdoc.cfg b/config/nimdoc.cfg index 11d9987d8..6b6ec2d83 100644 --- a/config/nimdoc.cfg +++ b/config/nimdoc.cfg @@ -1330,15 +1330,6 @@ dt pre > span.Operator ~ span.Identifier { background-repeat: no-repeat; background-image: url("data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAAAAUAAAAF////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAAAAIAAABbAAAAlQAAAKIAAACbAAAAmwAAAKIAAACVAAAAWwAAAAL///8A////AP///wD///8A////AAAAABQAAADAAAAAYwAAAA3///8A////AP///wD///8AAAAADQAAAGMAAADAAAAAFP///wD///8A////AP///wAAAACdAAAAOv///wD///8A////AP///wD///8A////AP///wD///8AAAAAOgAAAJ3///8A////AP///wAAAAAnAAAAcP///wAAAAAoAAAASv///wD///8A////AP///wAAAABKAAAAKP///wAAAABwAAAAJ////wD///8AAAAAgQAAABwAAACIAAAAkAAAAJMAAACtAAAAFQAAABUAAACtAAAAkwAAAJAAAACIAAAAHAAAAIH///8A////AAAAAKQAAACrAAAAaP///wD///8AAAAARQAAANIAAADSAAAARf///wD///8AAAAAaAAAAKsAAACk////AAAAADMAAACcAAAAnQAAABj///8A////AP///wAAAAAYAAAAGP///wD///8A////AAAAABgAAACdAAAAnAAAADMAAAB1AAAAwwAAAP8AAADpAAAAsQAAAE4AAAAb////AP///wAAAAAbAAAATgAAALEAAADpAAAA/wAAAMMAAAB1AAAAtwAAAOkAAAD/AAAA/wAAAP8AAADvAAAA3gAAAN4AAADeAAAA3gAAAO8AAAD/AAAA/wAAAP8AAADpAAAAtwAAAGUAAAA/AAAA3wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAADfAAAAPwAAAGX///8A////AAAAAEgAAADtAAAAvwAAAL0AAADGAAAA7wAAAO8AAADGAAAAvQAAAL8AAADtAAAASP///wD///8A////AP///wD///8AAAAAO////wD///8A////AAAAAIcAAACH////AP///wD///8AAAAAO////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A//8AAP//AAD4HwAA7/cAAN/7AAD//wAAoYUAAJ55AACf+QAAh+EAAAAAAADAAwAA4AcAAP5/AAD//wAA//8AAA=="); margin-bottom: -5px; } -div.pragma { - display: none; -} -span.pragmabegin { - cursor: pointer; -} -span.pragmaend { - cursor: pointer; -} div.search_results { background-color: antiquewhite; @@ -1351,32 +1342,47 @@ div#global-links ul { margin-left: 0; list-style-type: none; } + +span.pragmadots { + /* Position: relative frees us up to make the dots + look really nice without fucking up the layout and + causing bulging in the parent container */ + position: relative; + /* 1px down looks slightly nicer */ + top: 1px; + + padding: 2px; + background-color: #D3D3D3; + border-radius: 4px; + margin: 0 2px; + cursor: pointer; + + /* For some reason on Chrome, making the font size + smaller than 1em is causing the parent container to + bulge slightly. So, we're stuck with inheriting 1em, + which is sad, because 0.8em looks better... */ +} +span.pragmadots:hover { + background-color: #DBDBDB; +} +span.pragmawrap { + display: none; +} + </style> <script type="text/javascript" src="../dochack.js"></script> <script type="text/javascript"> -function togglepragma(d) { - if (d.style.display != 'inline') - d.style.display = 'inline'; - else - d.style.display = 'none'; -} - function main() { - var elements = document.getElementsByClassName("pragmabegin"); - for (var i = 0; i < elements.length; ++i) { - var e = elements[i]; - e.onclick = function(event) { - togglepragma(event.target.nextSibling); - }; - } - var elements = document.getElementsByClassName("pragmaend"); - for (var i = 0; i < elements.length; ++i) { - var e = elements[i]; - e.onclick = function(event) { - togglepragma(event.target.previousSibling); - }; + var pragmaDots = document.getElementsByClassName("pragmadots"); + for (var i = 0; i < pragmaDots.length; i++) { + pragmaDots[i].onclick = function(event) { + // Hide tease + event.target.parentNode.style.display = "none"; + // Show actual + event.target.parentNode.nextElementSibling.style.display = "inline"; + } } } </script> diff --git a/doc/astspec.txt b/doc/astspec.txt index 73058cd93..830dc7da9 100644 --- a/doc/astspec.txt +++ b/doc/astspec.txt @@ -1270,10 +1270,10 @@ AST: nnkIdent("float32"), nnkEmpty() ) - nnkPragma(nnkIdent("inline")), - nnkEmpty(), # reserved slot for future use - nnkStmtList(nnkDiscardStmt(nnkEmpty())) # the meat of the proc - ) + ), + nnkPragma(nnkIdent("inline")), + nnkEmpty(), # reserved slot for future use + nnkStmtList(nnkDiscardStmt(nnkEmpty())) # the meat of the proc ) There is another consideration. Nim has flexible type identification for diff --git a/doc/basicopt.txt b/doc/basicopt.txt index 90c7ba09c..a9166d36c 100644 --- a/doc/basicopt.txt +++ b/doc/basicopt.txt @@ -12,7 +12,8 @@ Options: -p, --path:PATH add path to search paths -d, --define:SYMBOL(:VAL) define a conditional symbol - (Optionally: Define the value for that symbol) + (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 --stackTrace:on|off turn stack tracing on|off diff --git a/doc/manual.rst b/doc/manual.rst index 4ab680326..7298b02a3 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -253,7 +253,7 @@ the exact spelling of an identifier. The exception with respect to the first letter allows common code like ``var foo: Foo`` to be parsed unambiguously. Historically, Nim was a fully `style-insensitive`:idx: language. This meant that -it was not case-sensitive and underscores were ignored and there was no even a +it was not case-sensitive and underscores were ignored and there was not even a distinction between ``foo`` and ``Foo``. diff --git a/doc/nimc.rst b/doc/nimc.rst index a3adcc143..0939f67e8 100644 --- a/doc/nimc.rst +++ b/doc/nimc.rst @@ -232,12 +232,12 @@ Cross compilation for Nintendo Switch ===================================== Simply add --os:nintendoswitch -to your usual ``nim c`` or ``nim cpp`` command and set the ``SWITCH_LIBS`` -and ``SWITCH_INCLUDES`` environment variables to something like: +to your usual ``nim c`` or ``nim cpp`` command and set the ``passC`` +and ``passL`` command line switches to something like: .. code-block:: console - export SWITCH_INCLUDES="-I$DEVKITPRO/libnx/include" - export SWITCH_LIBS="-specs=$DEVKITPRO/libnx/switch.specs -L$DEVKITPRO/libnx/lib -lnx" + nim c ... --passC="-I$DEVKITPRO/libnx/include" ... + --passL="-specs=$DEVKITPRO/libnx/switch.specs -L$DEVKITPRO/libnx/lib -lnx" or setup a nim.cfg file like so: @@ -259,10 +259,6 @@ an nro file with the ``elf2nro`` tool in the DevkitPro release. Examples can be `the nim-libnx github repo <https://github.com/jyapayne/nim-libnx.git>`_ or you can use `the switch builder tool <https://github.com/jyapayne/switch-builder.git>`_. -Environment variables are: - - ``SWITCH_LIBS`` for any extra libraries required by your application (``-lLIBNAME`` or ``-LLIBPATH``) - - ``SWITCH_INCLUDES`` for any extra include files (``-IINCLUDE_PATH``) - There are a few things that don't work because the DevkitPro libraries don't support them. They are: diff --git a/doc/tut1.rst b/doc/tut1.rst index aa6114cf7..67ef43948 100644 --- a/doc/tut1.rst +++ b/doc/tut1.rst @@ -891,7 +891,7 @@ important differences: future version of the compiler.) However, you can also use a ``closure`` iterator to get a different set of -restrictions. See `first class iterators <manual.html#first-class-iterators>`_ +restrictions. See `first class iterators <manual.html#iterators-and-the-for-statement-first-class-iterators>`_ for details. Iterators can have the same name and parameters as a proc, since essentially they have their own namespaces. Therefore it is common practice to wrap iterators in procs of the same name which accumulate the result of the diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 1f251b73e..8a1be3720 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -1284,7 +1284,9 @@ proc customPragmaNode(n: NimNode): NimNode = let typ = n.getTypeInst() - if typ.typeKind == ntyTypeDesc: + if typ.kind == nnkBracketExpr and typ.len > 1 and typ[1].kind == nnkProcTy: + return typ[1][1] + elif typ.typeKind == ntyTypeDesc: let impl = typ[1].getImpl() if impl[0].kind == nnkPragmaExpr: return impl[0][1] diff --git a/lib/deprecated/pure/asyncio.nim b/lib/deprecated/pure/asyncio.nim index 34cabefb0..161941e53 100644 --- a/lib/deprecated/pure/asyncio.nim +++ b/lib/deprecated/pure/asyncio.nim @@ -272,7 +272,7 @@ proc asyncSockHandleWrite(h: RootRef) = AsyncSocket(h).deleg.mode = fmRead when defined(ssl): - proc asyncSockDoHandshake(h: PObject) {.gcsafe.} = + proc asyncSockDoHandshake(h: RootRef) {.gcsafe.} = if AsyncSocket(h).socket.isSSL and not AsyncSocket(h).socket.gotHandshake: if AsyncSocket(h).sslNeedAccept: diff --git a/lib/posix/termios.nim b/lib/posix/termios.nim index 60d540107..c08de7342 100644 --- a/lib/posix/termios.nim +++ b/lib/posix/termios.nim @@ -122,6 +122,21 @@ var B9600* {.importc, header: "<termios.h>".}: Speed B19200* {.importc, header: "<termios.h>".}: Speed B38400* {.importc, header: "<termios.h>".}: Speed + B57600* {.importc, header: "<termios.h>".}: Speed + B115200* {.importc, header: "<termios.h>".}: Speed + B230400* {.importc, header: "<termios.h>".}: Speed + B460800* {.importc, header: "<termios.h>".}: Speed + B500000* {.importc, header: "<termios.h>".}: Speed + B576000* {.importc, header: "<termios.h>".}: Speed + B921600* {.importc, header: "<termios.h>".}: Speed + B1000000* {.importc, header: "<termios.h>".}: Speed + B1152000* {.importc, header: "<termios.h>".}: Speed + B1500000* {.importc, header: "<termios.h>".}: Speed + B2000000* {.importc, header: "<termios.h>".}: Speed + B2500000* {.importc, header: "<termios.h>".}: Speed + B3000000* {.importc, header: "<termios.h>".}: Speed + B3500000* {.importc, header: "<termios.h>".}: Speed + B4000000* {.importc, header: "<termios.h>".}: Speed EXTA* {.importc, header: "<termios.h>".}: Speed EXTB* {.importc, header: "<termios.h>".}: Speed CSIZE* {.importc, header: "<termios.h>".}: Cflag diff --git a/lib/pure/htmlparser.nim b/lib/pure/htmlparser.nim index 9d4b57dc0..c38c36874 100644 --- a/lib/pure/htmlparser.nim +++ b/lib/pure/htmlparser.nim @@ -1890,7 +1890,7 @@ proc entityToUtf8*(entity: string): string = proc addNode(father, son: XmlNode) = if son != nil: add(father, son) -proc parse(x: var XmlParser, errors: var seq[string]): XmlNode +proc parse(x: var XmlParser, errors: var seq[string]): XmlNode {.gcsafe.} proc expected(x: var XmlParser, n: XmlNode): string = result = errorMsg(x, "</" & n.tag & "> expected") diff --git a/lib/pure/includes/oserr.nim b/lib/pure/includes/oserr.nim index bfb6118f2..eb350cbd4 100644 --- a/lib/pure/includes/oserr.nim +++ b/lib/pure/includes/oserr.nim @@ -66,7 +66,7 @@ proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} = raise e {.push stackTrace:off.} -proc osLastError*(): OSErrorCode = +proc osLastError*(): OSErrorCode {.sideEffect.} = ## Retrieves the last operating system error code. ## ## This procedure is useful in the event when an OS call fails. In that case diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index bda0ecb77..0249b7413 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -322,8 +322,7 @@ type MemSlice* = object ## represent slice of a MemFile for iteration over deli proc `==`*(x, y: MemSlice): bool = ## Compare a pair of MemSlice for strict equality. - proc memcmp(a, b: pointer, n:int):int {.importc: "memcmp",header: "string.h".} - result = (x.size == y.size and memcmp(x.data, y.data, x.size) == 0) + result = (x.size == y.size and equalMem(x.data, y.data, x.size)) proc `$`*(ms: MemSlice): string {.inline.} = ## Return a Nim string built from a MemSlice. diff --git a/lib/pure/options.nim b/lib/pure/options.nim index bd01b208a..ce58943f9 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -189,7 +189,7 @@ proc `$`*[T](self: Option[T]): string = if self.isSome: "Some(" & $self.val & ")" else: - "None[" & T.name & "]" + "None[" & name(T) & "]" when isMainModule: import unittest, sequtils @@ -298,3 +298,17 @@ when isMainModule: test "none[T]": check(none[int]().isNone) check(none(int) == none[int]()) + + test "$ on typed with .name": + type Named = object + name: string + + let nobody = none(Named) + check($nobody == "None[Named]") + + test "$ on type with name()": + type Person = object + myname: string + + let noperson = none(Person) + check($noperson == "None[Person]") diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 6cf5e1fb7..84f492c9d 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -296,6 +296,26 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [].} = else: if chdir(newDir) != 0'i32: raiseOSError(osLastError()) +proc absolutePath*(path: string, root = getCurrentDir()): string = + ## Returns the absolute path of `path`, rooted at `root` (which must be absolute) + ## if `path` is absolute, return it, ignoring `root` + runnableExamples: + doAssert absolutePath("a") == getCurrentDir() / "a" + if isAbsolute(path): path + else: + if not root.isAbsolute: + raise newException(ValueError, "The specified root is not absolute: " & root) + joinPath(root, path) + +when isMainModule: + doAssertRaises(ValueError): discard absolutePath("a", "b") + doAssert absolutePath("a") == getCurrentDir() / "a" + doAssert absolutePath("a", "/b") == "/b" / "a" + when defined(Posix): + doAssert absolutePath("a", "/b/") == "/b" / "a" + doAssert absolutePath("a", "/b/c") == "/b/c" / "a" + doAssert absolutePath("/a", "b/") == "/a" + proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", tags: [ReadDirEffect].} = ## Returns the full (`absolute`:idx:) path of an existing file `filename`, diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim index 305052e41..4ae5afd6c 100644 --- a/lib/pure/ospaths.nim +++ b/lib/pure/ospaths.nim @@ -423,12 +423,22 @@ proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} = ## Checks whether a given `path` is absolute. ## ## On Windows, network paths are considered absolute too. + runnableExamples: + doAssert(not "".isAbsolute) + doAssert(not ".".isAbsolute) + when defined(posix): + doAssert "/".isAbsolute + doAssert(not "a/".isAbsolute) + + if len(path) == 0: return false + when doslikeFileSystem: var len = len(path) - result = (len > 0 and path[0] in {'/', '\\'}) or + result = (path[0] in {'/', '\\'}) or (len > 1 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':') elif defined(macos): - result = path.len > 0 and path[0] != ':' + # according to https://perldoc.perl.org/File/Spec/Mac.html `:a` is a relative path + result = path[0] != ':' elif defined(RISCOS): result = path[0] == '$' elif defined(posix): diff --git a/lib/pure/strformat.nim b/lib/pure/strformat.nim index 247b9ec5c..f13eb5e8e 100644 --- a/lib/pure/strformat.nim +++ b/lib/pure/strformat.nim @@ -683,8 +683,8 @@ when isMainModule: # works: import times - var nullTime: DateTime - check &"{nullTime:yyyy-mm-dd}", "0000-00-00" + var dt = initDateTime(01, mJan, 2000, 00, 00, 00) + check &"{dt:yyyy-MM-dd}", "2000-01-01" var tm = fromUnix(0) discard &"{tm}" diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index ac41a0aad..2e138b27e 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -19,22 +19,30 @@ import macros import strformat from strutils import toLowerAscii -import colors +import colors, tables -const - hasThreadSupport = compileOption("threads") +when defined(windows): + import winlean -when not hasThreadSupport: - import tables - var - colorsFGCache = initTable[Color, string]() - colorsBGCache = initTable[Color, string]() - styleCache = initTable[int, string]() +type + PTerminal = ref object + trueColorIsSupported: bool + trueColorIsEnabled: bool + fgSetColor: bool + when defined(windows): + hStdout: Handle + hStderr: Handle + oldStdoutAttr: int16 + oldStderrAttr: int16 -var - trueColorIsSupported: bool - trueColorIsEnabled: bool - fgSetColor: bool +var gTerm {.threadvar.}: PTerminal + +proc newTerminal(): PTerminal + +proc getTerminal(): PTerminal {.inline.} = + if isNil(gTerm): + gTerm = newTerminal() + result = gTerm const fgPrefix = "\x1b[38;2;" @@ -156,23 +164,6 @@ when defined(windows): proc setConsoleMode(hConsoleHandle: Handle, dwMode: DWORD): WINBOOL{. stdcall, dynlib: "kernel32", importc: "SetConsoleMode".} - var - hStdout: Handle # = createFile("CONOUT$", GENERIC_WRITE, 0, nil, - # OPEN_ALWAYS, 0, 0) - hStderr: Handle - - block: - var hStdoutTemp = getStdHandle(STD_OUTPUT_HANDLE) - if duplicateHandle(getCurrentProcess(), hStdoutTemp, getCurrentProcess(), - addr(hStdout), 0, 1, DUPLICATE_SAME_ACCESS) == 0: - when defined(consoleapp): - raiseOSError(osLastError()) - var hStderrTemp = getStdHandle(STD_ERROR_HANDLE) - if duplicateHandle(getCurrentProcess(), hStderrTemp, getCurrentProcess(), - addr(hStderr), 0, 1, DUPLICATE_SAME_ACCESS) == 0: - when defined(consoleapp): - raiseOSError(osLastError()) - proc getCursorPos(h: Handle): tuple [x,y: int] = var c: CONSOLESCREENBUFFERINFO if getConsoleScreenBufferInfo(h, addr(c)) == 0: @@ -193,12 +184,23 @@ when defined(windows): return c.wAttributes return 0x70'i16 # ERROR: return white background, black text - var - oldStdoutAttr = getAttributes(hStdout) - oldStderrAttr = getAttributes(hStderr) + proc initTerminal(term: PTerminal) = + var hStdoutTemp = getStdHandle(STD_OUTPUT_HANDLE) + if duplicateHandle(getCurrentProcess(), hStdoutTemp, getCurrentProcess(), + addr(term.hStdout), 0, 1, DUPLICATE_SAME_ACCESS) == 0: + when defined(consoleapp): + raiseOSError(osLastError()) + var hStderrTemp = getStdHandle(STD_ERROR_HANDLE) + if duplicateHandle(getCurrentProcess(), hStderrTemp, getCurrentProcess(), + addr(term.hStderr), 0, 1, DUPLICATE_SAME_ACCESS) == 0: + when defined(consoleapp): + raiseOSError(osLastError()) + term.oldStdoutAttr = getAttributes(term.hStdout) + term.oldStderrAttr = getAttributes(term.hStderr) template conHandle(f: File): Handle = - if f == stderr: hStderr else: hStdout + let term = getTerminal() + if f == stderr: term.hStderr else: term.hStdout else: import termios, posix, os, parseutils @@ -306,7 +308,7 @@ proc setCursorPos*(f: File, x, y: int) = let h = conHandle(f) setCursorPos(h, x, y) else: - f.write(fmt"{stylePrefix}{y};{x}f") + f.write(fmt"{stylePrefix}{y+1};{x+1}f") proc setCursorXPos*(f: File, x: int) = ## Sets the terminal's cursor to the x position. @@ -321,7 +323,7 @@ proc setCursorXPos*(f: File, x: int) = if setConsoleCursorPosition(h, origin) == 0: raiseOSError(osLastError()) else: - f.write(fmt"{stylePrefix}{x}G") + f.write(fmt"{stylePrefix}{x+1}G") when defined(windows): proc setCursorYPos*(f: File, y: int) = @@ -459,10 +461,11 @@ proc eraseScreen*(f: File) = proc resetAttributes*(f: File) = ## Resets all attributes. when defined(windows): + let term = getTerminal() if f == stderr: - discard setConsoleTextAttribute(hStderr, oldStderrAttr) + discard setConsoleTextAttribute(term.hStderr, term.oldStderrAttr) else: - discard setConsoleTextAttribute(hStdout, oldStdoutAttr) + discard setConsoleTextAttribute(term.hStdout, term.oldStdoutAttr) else: f.write(ansiResetCode) @@ -471,11 +474,12 @@ type styleBright = 1, ## bright text styleDim, ## dim text styleItalic, ## italic (or reverse on terminals not supporting) - styleUnderscore = 4, ## underscored text + styleUnderscore, ## underscored text styleBlink, ## blinking/bold text - styleReverse = 7, ## reverse - styleHidden ## hidden text - styleStrikethrough, ## strikethrough + styleBlinkRapid, ## rapid blinking/bold text (not widely supported) + styleReverse, ## reverse + styleHidden, ## hidden text + styleStrikethrough ## strikethrough {.deprecated: [TStyle: Style].} {.deprecated: [styleUnknown: styleItalic].} @@ -486,14 +490,7 @@ when not defined(windows): gBG {.threadvar.}: int proc ansiStyleCode*(style: int): string = - when hasThreadSupport: - result = fmt"{stylePrefix}{style}m" - else: - if styleCache.hasKey(style): - result = styleCache[style] - else: - result = fmt"{stylePrefix}{style}m" - styleCache[style] = result + result = fmt"{stylePrefix}{style}m" template ansiStyleCode*(style: Style): string = ansiStyleCode(style.int) @@ -520,10 +517,11 @@ proc setStyle*(f: File, style: set[Style]) = proc writeStyled*(txt: string, style: set[Style] = {styleBright}) = ## Writes the text `txt` in a given `style` to stdout. when defined(windows): - var old = getAttributes(hStdout) + let term = getTerminal() + var old = getAttributes(term.hStdout) stdout.setStyle(style) stdout.write(txt) - discard setConsoleTextAttribute(hStdout, old) + discard setConsoleTextAttribute(term.hStdout, old) else: stdout.setStyle(style) stdout.write(txt) @@ -632,32 +630,16 @@ template ansiForegroundColorCode*(fg: static[ForegroundColor], ansiStyleCode(fg.int + bright.int * 60) proc ansiForegroundColorCode*(color: Color): string = - when hasThreadSupport: - let rgb = extractRGB(color) - result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m" - else: - if colorsFGCache.hasKey(color): - result = colorsFGCache[color] - else: - let rgb = extractRGB(color) - result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m" - colorsFGCache[color] = result + let rgb = extractRGB(color) + result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m" template ansiForegroundColorCode*(color: static[Color]): string = const rgb = extractRGB(color) (static(fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m")) proc ansiBackgroundColorCode*(color: Color): string = - when hasThreadSupport: - let rgb = extractRGB(color) - result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m" - else: - if colorsBGCache.hasKey(color): - result = colorsBGCache[color] - else: - let rgb = extractRGB(color) - result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m" - colorsFGCache[color] = result + let rgb = extractRGB(color) + result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m" template ansiBackgroundColorCode*(color: static[Color]): string = const rgb = extractRGB(color) @@ -665,16 +647,17 @@ template ansiBackgroundColorCode*(color: static[Color]): string = proc setForegroundColor*(f: File, color: Color) = ## Sets the terminal's foreground true color. - if trueColorIsEnabled: + if getTerminal().trueColorIsEnabled: f.write(ansiForegroundColorCode(color)) proc setBackgroundColor*(f: File, color: Color) = ## Sets the terminal's background true color. - if trueColorIsEnabled: + if getTerminal().trueColorIsEnabled: f.write(ansiBackgroundColorCode(color)) proc setTrueColor(f: File, color: Color) = - if fgSetColor: + let term = getTerminal() + if term.fgSetColor: setForegroundColor(f, color) else: setBackgroundColor(f, color) @@ -726,11 +709,10 @@ macro styledWrite*(f: File, m: varargs[typed]): untyped = ## stdout.styledWrite(fgRed, "red text ") ## stdout.styledWrite(fgGreen, "green text") ## - let m = callsite() var reset = false result = newNimNode(nnkStmtList) - for i in countup(2, m.len - 1): + for i in countup(0, m.len - 1): let item = m[i] case item.kind of nnkStrLit..nnkTripleStrLit: @@ -871,56 +853,65 @@ proc resetAttributes*() {.noconv.} = proc isTrueColorSupported*(): bool = ## Returns true if a terminal supports true color. - return trueColorIsSupported + return getTerminal().trueColorIsSupported when defined(windows): import os proc enableTrueColors*() = ## Enable true color. + var term = getTerminal() when defined(windows): var ver: OSVERSIONINFO ver.dwOSVersionInfoSize = sizeof(ver).DWORD let res = getVersionExW(addr ver) if res == 0: - trueColorIsSupported = false + term.trueColorIsSupported = false else: - trueColorIsSupported = ver.dwMajorVersion > 10 or + term.trueColorIsSupported = ver.dwMajorVersion > 10 or (ver.dwMajorVersion == 10 and (ver.dwMinorVersion > 0 or (ver.dwMinorVersion == 0 and ver.dwBuildNumber >= 10586))) - if not trueColorIsSupported: - trueColorIsSupported = getEnv("ANSICON_DEF").len > 0 + if not term.trueColorIsSupported: + term.trueColorIsSupported = getEnv("ANSICON_DEF").len > 0 - if trueColorIsSupported: + if term.trueColorIsSupported: if getEnv("ANSICON_DEF").len == 0: var mode: DWORD = 0 if getConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), addr(mode)) != 0: mode = mode or ENABLE_VIRTUAL_TERMINAL_PROCESSING if setConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), mode) != 0: - trueColorIsEnabled = true + term.trueColorIsEnabled = true else: - trueColorIsEnabled = false + term.trueColorIsEnabled = false else: - trueColorIsEnabled = true + term.trueColorIsEnabled = true else: - trueColorIsSupported = string(getEnv("COLORTERM")).toLowerAscii() in ["truecolor", "24bit"] - trueColorIsEnabled = trueColorIsSupported + term.trueColorIsSupported = string(getEnv("COLORTERM")).toLowerAscii() in ["truecolor", "24bit"] + term.trueColorIsEnabled = term.trueColorIsSupported proc disableTrueColors*() = ## Disable true color. + var term = getTerminal() when defined(windows): - if trueColorIsSupported: + if term.trueColorIsSupported: if getEnv("ANSICON_DEF").len == 0: var mode: DWORD = 0 if getConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), addr(mode)) != 0: mode = mode and not ENABLE_VIRTUAL_TERMINAL_PROCESSING discard setConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), mode) - trueColorIsEnabled = false + term.trueColorIsEnabled = false else: - trueColorIsEnabled = false + term.trueColorIsEnabled = false + +proc newTerminal(): PTerminal = + new result + when defined(windows): + initTerminal(result) when not defined(testing) and isMainModule: + assert ansiStyleCode(styleBright) == "\e[1m" + assert ansiStyleCode(styleStrikethrough) == "\e[9m" #system.addQuitProc(resetAttributes) write(stdout, "never mind") stdout.eraseLine() diff --git a/lib/pure/times.nim b/lib/pure/times.nim index a134faef2..bc4de7ee4 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -7,35 +7,127 @@ # 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 <backends.html#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). + + Examples: + + .. code-block:: nim + + import times, os + let time = cpuTime() + + 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() + + echo "cpuTime() float value: ", cpuTime() + echo "An hour from now : ", now() + 1.hours + echo "An hour from (UTC) now: ", getTime().utc + initDuration(hours = 1) + + Parsing and Formatting Dates + ---------------------------- + + The ``DateTime`` type can be parsed and formatted using the different + ``parse`` and ``format`` procedures. + + .. code-block:: nim + + let dt = parse("2000-01-01", "yyyy-MM-dd") + echo dt.format("yyyy-MM-dd") + + The different format patterns that are supported are documented below. + + ============= ================================================================================= ================================================ + Pattern Description Example + ============= ================================================================================= ================================================ + ``d`` Numeric value representing the day of the month, | ``1/04/2012 -> 1`` + it will be either one or two digits long. | ``21/04/2012 -> 21`` + ``dd`` Same as above, but is always two digits. | ``1/04/2012 -> 01`` + | ``21/04/2012 -> 21`` + ``ddd`` Three letter string which indicates the day of the week. | ``Saturday -> Sat`` + | ``Monday -> Mon`` + ``dddd`` Full string for the day of the week. | ``Saturday -> Saturday`` + | ``Monday -> Monday`` + ``h`` The hours in one digit if possible. Ranging from 1-12. | ``5pm -> 5`` + | ``2am -> 2`` + ``hh`` The hours in two digits always. If the hour is one digit 0 is prepended. | ``5pm -> 05`` + | ``11am -> 11`` + ``H`` The hours in one digit if possible, ranging from 0-23. | ``5pm -> 17`` + | ``2am -> 2`` + ``HH`` The hours in two digits always. 0 is prepended if the hour is one digit. | ``5pm -> 17`` + | ``2am -> 02`` + ``m`` The minutes in 1 digit if possible. | ``5:30 -> 30`` + | ``2:01 -> 1`` + ``mm`` Same as above but always 2 digits, 0 is prepended if the minute is one digit. | ``5:30 -> 30`` + | ``2:01 -> 01`` + ``M`` The month in one digit if possible. | ``September -> 9`` + | ``December -> 12`` + ``MM`` The month in two digits always. 0 is prepended. | ``September -> 09`` + | ``December -> 12`` + ``MMM`` Abbreviated three-letter form of the month. | ``September -> Sep`` + | ``December -> Dec`` + ``MMMM`` Full month string, properly capitalized. | ``September -> September`` + ``s`` Seconds as one digit if possible. | ``00:00:06 -> 6`` + ``ss`` Same as above but always two digits. 0 is prepended. | ``00:00:06 -> 06`` + ``t`` ``A`` when time is in the AM. ``P`` when time is in the PM. | ``5pm -> P`` + | ``2am -> A`` + ``tt`` Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively. | ``5pm -> PM`` + | ``2am -> AM`` + ``yy`` The last two digits of the year. When parsing, the current century is assumed. | ``2012 AD -> 12`` + ``yyyy`` The year, padded to atleast four digits. | ``2012 AD -> 2012`` + Is always positive, even when the year is BC. | ``24 AD -> 0024`` + When the year is more than four digits, '+' is prepended. | ``24 BC -> 00024`` + | ``12345 AD -> +12345`` + ``YYYY`` The year without any padding. | ``2012 AD -> 2012`` + Is always positive, even when the year is BC. | ``24 AD -> 24`` + | ``24 BC -> 24`` + | ``12345 AD -> 12345`` + ``uuuu`` The year, padded to atleast four digits. Will be negative when the year is BC. | ``2012 AD -> 2012`` + When the year is more than four digits, '+' is prepended unless the year is BC. | ``24 AD -> 0024`` + | ``24 BC -> -0023`` + | ``12345 AD -> +12345`` + ``UUUU`` The year without any padding. Will be negative when the year is BC. | ``2012 AD -> 2012`` + | ``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`` + ``g`` Era: AD or BC | ``300 AD -> AD`` + | ``300 BC -> BC`` + ``fff`` Milliseconds display | ``1000000 nanoseconds -> 1`` + ``ffffff`` Microseconds display | ``1000000 nanoseconds -> 1000`` + ``fffffffff`` Nanoseconds display | ``1000000 nanoseconds -> 1000000`` + ============= ================================================================================= ================================================ + + Other strings can be inserted by putting them in ``''``. For example + ``hh'->'mm`` will give ``01->56``. The following characters can be + inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` + ``,``. A literal ``'`` can be specified with ``''``. + + However you don't need to necessarily separate format patterns, a + unambiguous format string like ``yyyyMMddhhmmss`` is valid too (although + only for years in the range 1..9999). +]## -## This module contains routines and types for dealing with time using a proleptic Gregorian calendar. -## It's is available for the `JavaScript target <backends.html#the-javascript-target>`_. -## -## The types uses nanosecond time resolution, but the underlying resolution used by ``getTime()`` -## depends on the platform and backend (JS is limited to millisecond precision). -## -## Examples: -## -## .. code-block:: nim -## -## import times, os -## let time = cpuTime() -## -## 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() -## -## echo "cpuTime() float value: ", cpuTime() -## echo "An hour from now : ", now() + 1.hours -## echo "An hour from (UTC) now: ", getTime().utc + initDuration(hours = 1) {.push debugger:off.} # the user does not want to trace a part # of the standard library! import - strutils, parseutils, algorithm, math + strutils, parseutils, algorithm, math, options, strformat include "system/inclrtl" @@ -306,7 +398,7 @@ proc fractional*(dur: Duration): Duration {.inline.} = 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:00+00:00" + doAssert $fromUnix(0).utc == "1970-01-01T00:00:00Z" initTime(unix, 0) proc toUnix*(t: Time): int64 {.benign, tags: [], raises: [], noSideEffect.} = @@ -915,7 +1007,7 @@ proc initTimeInterval*(nanoseconds, microseconds, milliseconds, runnableExamples: let day = initTimeInterval(hours=24) let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc()) - doAssert $(dt + day) == "2000-01-02T12:00:00+00:00" + doAssert $(dt + day) == "2000-01-02T12:00:00Z" result.nanoseconds = nanoseconds result.microseconds = microseconds result.milliseconds = milliseconds @@ -1126,7 +1218,7 @@ proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, ## Create a new ``DateTime`` in the specified timezone. runnableExamples: let dt1 = initDateTime(30, mMar, 2017, 00, 00, 00, 00, utc()) - doAssert $dt1 == "2017-03-30T00:00:00+00:00" + doAssert $dt1 == "2017-03-30T00:00:00Z" assertValidDate monthday, month, year let dt = DateTime( @@ -1146,7 +1238,7 @@ proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, ## Create a new ``DateTime`` in the specified timezone. runnableExamples: let dt1 = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) - doAssert $dt1 == "2017-03-30T00:00:00+00:00" + doAssert $dt1 == "2017-03-30T00:00:00Z" initDateTime(monthday, month, year, hour, minute, second, 0, zone) @@ -1162,9 +1254,9 @@ proc `+`*(dt: DateTime, interval: TimeInterval): DateTime = ## runnableExamples: let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) - doAssert $(dt + 1.months) == "2017-04-30T00:00:00+00:00" + doAssert $(dt + 1.months) == "2017-04-30T00:00:00Z" # This is correct and happens due to monthday overflow. - doAssert $(dt - 1.months) == "2017-03-02T00:00:00+00:00" + doAssert $(dt - 1.months) == "2017-03-02T00:00:00Z" let (adjDur, absDur) = evaluateInterval(dt, interval) if adjDur != DurationZero: @@ -1185,7 +1277,7 @@ proc `-`*(dt: DateTime, interval: TimeInterval): DateTime = ## 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:00+00:00" + doAssert $(dt - 5.days) == "2017-03-25T00:00:00Z" dt + (-interval) @@ -1193,7 +1285,7 @@ proc `+`*(dt: DateTime, dur: Duration): DateTime = runnableExamples: let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) let dur = initDuration(hours = 5) - doAssert $(dt + dur) == "2017-03-30T05:00:00+00:00" + doAssert $(dt + dur) == "2017-03-30T05:00:00Z" (dt.toTime + dur).inZone(dt.timezone) @@ -1201,7 +1293,7 @@ proc `-`*(dt: DateTime, dur: Duration): DateTime = runnableExamples: let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) let dur = initDuration(days = 5) - doAssert $(dt - dur) == "2017-03-25T00:00:00+00:00" + doAssert $(dt - dur) == "2017-03-25T00:00:00Z" (dt.toTime - dur).inZone(dt.timezone) @@ -1394,228 +1486,726 @@ proc `*=`*[T: TimesMutableTypes, U](a: var T, b: U) = a = a * b -proc formatToken(dt: DateTime, token: string, buf: var string) = - ## Helper of the format proc to parse individual tokens. - ## - ## Pass the found token in the user input string, and the buffer where the - ## final string is being built. This has to be a var value because certain - ## formatting tokens require modifying the previous characters. - case token - of "d": - buf.add($dt.monthday) - of "dd": - if dt.monthday < 10: - buf.add("0") - buf.add($dt.monthday) - of "ddd": - buf.add(($dt.weekday)[0 .. 2]) - of "dddd": - buf.add($dt.weekday) - of "h": - if dt.hour == 0: buf.add("12") - else: buf.add($(if dt.hour > 12: dt.hour - 12 else: dt.hour)) - of "hh": - if dt.hour == 0: - buf.add("12") - else: - let amerHour = if dt.hour > 12: dt.hour - 12 else: dt.hour - if amerHour < 10: - buf.add('0') - buf.add($amerHour) - of "H": - buf.add($dt.hour) - of "HH": - if dt.hour < 10: - buf.add('0') - buf.add($dt.hour) - of "m": - buf.add($dt.minute) - of "mm": - if dt.minute < 10: - buf.add('0') - buf.add($dt.minute) - of "M": - buf.add($ord(dt.month)) - of "MM": - if dt.month < mOct: - buf.add('0') - buf.add($ord(dt.month)) - of "MMM": - buf.add(($dt.month)[0..2]) - of "MMMM": - buf.add($dt.month) - of "s": - buf.add($dt.second) - of "ss": - if dt.second < 10: - buf.add('0') - buf.add($dt.second) - of "t": - if dt.hour >= 12: - buf.add('P') - else: buf.add('A') - of "tt": - if dt.hour >= 12: - buf.add("PM") - else: buf.add("AM") - of "y": - var fr = ($dt.year).len()-1 - if fr < 0: fr = 0 - buf.add(($dt.year)[fr .. ($dt.year).len()-1]) - of "yy": - var fr = ($dt.year).len()-2 - if fr < 0: fr = 0 - var fyear = ($dt.year)[fr .. ($dt.year).len()-1] - if fyear.len != 2: fyear = repeat('0', 2-fyear.len()) & fyear - buf.add(fyear) - of "yyy": - var fr = ($dt.year).len()-3 - if fr < 0: fr = 0 - var fyear = ($dt.year)[fr .. ($dt.year).len()-1] - if fyear.len != 3: fyear = repeat('0', 3-fyear.len()) & fyear - buf.add(fyear) - of "yyyy": - var fr = ($dt.year).len()-4 - if fr < 0: fr = 0 - var fyear = ($dt.year)[fr .. ($dt.year).len()-1] - if fyear.len != 4: fyear = repeat('0', 4-fyear.len()) & fyear - buf.add(fyear) - of "yyyyy": - var fr = ($dt.year).len()-5 - if fr < 0: fr = 0 - var fyear = ($dt.year)[fr .. ($dt.year).len()-1] - if fyear.len != 5: fyear = repeat('0', 5-fyear.len()) & fyear - buf.add(fyear) - of "z": - let - nonDstTz = dt.utcOffset - hours = abs(nonDstTz) div secondsInHour - if nonDstTz <= 0: buf.add('+') - else: buf.add('-') - buf.add($hours) - of "zz": - let - nonDstTz = dt.utcOffset - hours = abs(nonDstTz) div secondsInHour - if nonDstTz <= 0: buf.add('+') - else: buf.add('-') - if hours < 10: buf.add('0') - buf.add($hours) - of "zzz": - let - nonDstTz = dt.utcOffset - hours = abs(nonDstTz) div secondsInHour - minutes = (abs(nonDstTz) div secondsInMin) mod minutesInHour - if nonDstTz <= 0: buf.add('+') - else: buf.add('-') - if hours < 10: buf.add('0') - buf.add($hours) - buf.add(':') - if minutes < 10: buf.add('0') - buf.add($minutes) - of "fff": - buf.add(intToStr(convert(Nanoseconds, Milliseconds, dt.nanosecond), 3)) - of "ffffff": - buf.add(intToStr(convert(Nanoseconds, Microseconds, dt.nanosecond), 6)) - of "fffffffff": - buf.add(intToStr(dt.nanosecond, 9)) - of "": - discard - else: - raise newException(ValueError, "Invalid format string: " & token) +# +# Parse & format implementation +# -proc format*(dt: DateTime, f: string): string {.tags: [].}= - ## This procedure formats `dt` as specified by `f`. The following format - ## specifiers are available: - ## - ## ============ ================================================================================= ================================================ - ## Specifier Description Example - ## ============ ================================================================================= ================================================ - ## d Numeric value of the day of the month, it will be one or two digits long. ``1/04/2012 -> 1``, ``21/04/2012 -> 21`` - ## dd Same as above, but always two digits. ``1/04/2012 -> 01``, ``21/04/2012 -> 21`` - ## ddd Three letter string which indicates the day of the week. ``Saturday -> Sat``, ``Monday -> Mon`` - ## dddd Full string for the day of the week. ``Saturday -> Saturday``, ``Monday -> Monday`` - ## h The hours in one digit if possible. Ranging from 0-12. ``5pm -> 5``, ``2am -> 2`` - ## hh The hours in two digits always. If the hour is one digit 0 is prepended. ``5pm -> 05``, ``11am -> 11`` - ## H The hours in one digit if possible, randing from 0-24. ``5pm -> 17``, ``2am -> 2`` - ## HH The hours in two digits always. 0 is prepended if the hour is one digit. ``5pm -> 17``, ``2am -> 02`` - ## m The minutes in 1 digit if possible. ``5:30 -> 30``, ``2:01 -> 1`` - ## mm Same as above but always 2 digits, 0 is prepended if the minute is one digit. ``5:30 -> 30``, ``2:01 -> 01`` - ## M The month in one digit if possible. ``September -> 9``, ``December -> 12`` - ## MM The month in two digits always. 0 is prepended. ``September -> 09``, ``December -> 12`` - ## MMM Abbreviated three-letter form of the month. ``September -> Sep``, ``December -> Dec`` - ## MMMM Full month string, properly capitalized. ``September -> September`` - ## s Seconds as one digit if possible. ``00:00:06 -> 6`` - ## ss Same as above but always two digits. 0 is prepended. ``00:00:06 -> 06`` - ## t ``A`` when time is in the AM. ``P`` when time is in the PM. - ## tt Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively. - ## y(yyyy) This displays the year to different digits. You most likely only want 2 or 4 'y's - ## yy Displays the year to two digits. ``2012 -> 12`` - ## yyyy Displays the year to four digits. ``2012 -> 2012`` - ## z Displays the timezone offset from UTC. ``GMT+7 -> +7``, ``GMT-5 -> -5`` - ## 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`` - ## fff Milliseconds display ``1000000 nanoseconds -> 1`` - ## ffffff Microseconds display ``1000000 nanoseconds -> 1000`` - ## fffffffff Nanoseconds display ``1000000 nanoseconds -> 1000000`` - ## ============ ================================================================================= ================================================ - ## - ## Other strings can be inserted by putting them in ``''``. For example - ## ``hh'->'mm`` will give ``01->56``. The following characters can be - ## inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` - ## ``,``. However you don't need to necessarily separate format specifiers, a - ## unambiguous format string like ``yyyyMMddhhmmss`` is valid too. +type + AmPm = enum + apUnknown, apAm, apPm + + Era = enum + eraUnknown, eraAd, eraBc + + ParsedTime = object + amPm: AmPm + era: Era + year: Option[int] + month: Option[int] + monthday: Option[int] + utcOffset: Option[int] + + # '0' as default for these work fine + # so no need for `Option`. + hour: int + minute: int + second: int + nanosecond: int + + FormatTokenKind = enum + tkPattern, tkLiteral + + FormatPattern {.pure.} = enum + d, dd, ddd, dddd + h, hh, H, HH + m, mm, M, MM, MMM, MMMM + s, ss + fff, ffffff, fffffffff + t, tt + y, yy, yyy, yyyy, yyyyy + YYYY + uuuu + UUUU + z, zz, zzz, zzzz + g + + # This is a special value used to mark literal format values. + # See the doc comment for ``TimeFormat.patterns``. + Lit + + TimeFormat* = object ## Represents a format for parsing and printing + ## time types. + patterns: seq[byte] ## \ + ## Contains the patterns encoded as bytes. + ## Literal values are encoded in a special way. + ## They start with ``Lit.byte``, then the length of the literal, then the + ## raw char values of the literal. For example, the literal `foo` would + ## be encoded as ``@[Lit.byte, 3.byte, 'f'.byte, 'o'.byte, 'o'.byte]``. + formatStr: string + +const FormatLiterals = { ' ', '-', '/', ':', '(', ')', '[', ']', ',' } + +proc `$`*(f: TimeFormat): string = + ## Returns the format string that was used to construct ``f``. runnableExamples: - let dt = initDateTime(01, mJan, 2000, 12, 00, 00, 01, utc()) - doAssert format(dt, "yyyy-MM-dd'T'HH:mm:ss'.'fffffffffzzz") == "2000-01-01T12:00:00.000000001+00:00" + let f = initTimeFormat("yyyy-MM-dd") + doAssert $f == "yyyy-MM-dd" + f.formatStr - result = "" +proc raiseParseException(f: TimeFormat, input: string, msg: string) = + raise newException(ValueError, + &"Failed to parse '{input}' with format '{f}'. {msg}") + +iterator tokens(f: string): tuple[kind: FormatTokenKind, token: string] = var i = 0 - var currentF = "" - while i < f.len: - case f[i] - of ' ', '-', '/', ':', '\'', '(', ')', '[', ']', ',': - formatToken(dt, currentF, result) + var currToken = "" - currentF = "" + template yieldCurrToken() = + if currToken.len != 0: + yield (tkPattern, currToken) + currToken = "" - if f[i] == '\'': + while i < f.len: + case f[i] + of '\'': + yieldCurrToken() + if i.succ < f.len and f[i.succ] == '\'': + yield (tkLiteral, "'") + i.inc 2 + else: + var token = "" inc(i) # Skip ' - while i < f.len-1 and f[i] != '\'': - result.add(f[i]) - inc(i) - else: result.add(f[i]) - + while i < f.len and f[i] != '\'': + token.add f[i] + i.inc + + if i > f.high: + raise newException(ValueError, + &"Unclosed ' in time format string. " & + "For a literal ', use ''.") + i.inc + yield (tkLiteral, token) + of FormatLiterals: + yieldCurrToken() + yield (tkLiteral, $f[i]) + i.inc else: # Check if the letter being added matches previous accumulated buffer. - if currentF.len == 0 or currentF[high(currentF)] == f[i]: - currentF.add(f[i]) + if currToken.len == 0 or currToken[0] == f[i]: + currToken.add(f[i]) + i.inc + else: + yield (tkPattern, currToken) + currToken = $f[i] + i.inc + + yieldCurrToken() + +proc stringToPattern(str: string): FormatPattern = + case str + of "d": result = d + of "dd": result = dd + of "ddd": result = ddd + of "dddd": result = dddd + of "h": result = h + of "hh": result = hh + of "H": result = H + of "HH": result = HH + of "m": result = m + of "mm": result = mm + of "M": result = M + of "MM": result = MM + of "MMM": result = MMM + of "MMMM": result = MMMM + of "s": result = s + of "ss": result = ss + of "fff": result = fff + of "ffffff": result = ffffff + of "fffffffff": result = fffffffff + of "t": result = t + of "tt": result = tt + of "y": result = y + of "yy": result = yy + of "yyy": result = yyy + of "yyyy": result = yyyy + of "yyyyy": result = yyyyy + of "YYYY": result = YYYY + of "uuuu": result = uuuu + of "UUUU": result = UUUU + of "z": result = z + of "zz": result = zz + of "zzz": result = zzz + of "zzzz": result = zzzz + of "g": result = g + else: raise newException(ValueError, &"'{str}' is not a valid pattern") + +proc initTimeFormat*(format: string): TimeFormat = + ## Construct a new time format for parsing & formatting time types. + ## + ## See `Parsing and formatting dates`_ for documentation of the + ## ``format`` argument. + runnableExamples: + let f = initTimeFormat("yyyy-MM-dd") + doAssert "2000-01-01" == "2000-01-01".parse(f).format(f) + result.formatStr = format + result.patterns = @[] + for kind, token in format.tokens: + case kind + of tkLiteral: + case token + else: + result.patterns.add(FormatPattern.Lit.byte) + if token.len > 255: + raise newException(ValueError, + "Format literal is to long:" & token) + result.patterns.add(token.len.byte) + for c in token: + result.patterns.add(c.byte) + of tkPattern: + result.patterns.add(stringToPattern(token).byte) + +proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string) = + template yearOfEra(dt: DateTime): int = + if dt.year <= 0: abs(dt.year) + 1 else: dt.year + + case pattern + of d: + result.add $dt.monthday + of dd: + result.add dt.monthday.intToStr(2) + of ddd: + result.add ($dt.weekday)[0..2] + of dddd: + result.add $dt.weekday + of h: + result.add( + if dt.hour == 0: "12" + elif dt.hour > 12: $(dt.hour - 12) + else: $dt.hour + ) + of hh: + result.add( + if dt.hour == 0: "12" + elif dt.hour > 12: (dt.hour - 12).intToStr(2) + else: dt.hour.intToStr(2) + ) + of H: + result.add $dt.hour + of HH: + result.add dt.hour.intToStr(2) + of m: + result.add $dt.minute + of mm: + result.add dt.minute.intToStr(2) + of M: + result.add $ord(dt.month) + of MM: + result.add ord(dt.month).intToStr(2) + of MMM: + result.add ($dt.month)[0..2] + of MMMM: + result.add $dt.month + of s: + result.add $dt.second + of ss: + result.add dt.second.intToStr(2) + of fff: + result.add(intToStr(convert(Nanoseconds, Milliseconds, dt.nanosecond), 3)) + of ffffff: + result.add(intToStr(convert(Nanoseconds, Microseconds, dt.nanosecond), 6)) + of fffffffff: + result.add(intToStr(dt.nanosecond, 9)) + of t: + result.add if dt.hour >= 12: "P" else: "A" + of tt: + result.add if dt.hour >= 12: "PM" else: "AM" + of y: # Deprecated + result.add $(dt.yearOfEra mod 10) + of yy: + result.add (dt.yearOfEra mod 100).intToStr(2) + of yyy: # Deprecated + result.add (dt.yearOfEra mod 1000).intToStr(3) + of yyyy: + let year = dt.yearOfEra + if year < 10000: + result.add year.intToStr(4) + else: + result.add '+' & $year + of yyyyy: # Deprecated + result.add (dt.yearOfEra mod 100_000).intToStr(5) + of YYYY: + if dt.year < 1: + result.add $(abs(dt.year) + 1) + else: + result.add $dt.year + of uuuu: + let year = dt.year + if year < 10000 or year < 0: + result.add year.intToStr(4) + else: + result.add '+' & $year + of UUUU: + result.add $dt.year + of z, zz, zzz, zzzz: + if dt.timezone.name == "Etc/UTC": + result.add 'Z' + else: + result.add if -dt.utcOffset >= 0: '+' else: '-' + let absOffset = abs(dt.utcOffset) + case pattern: + of z: + result.add $(absOffset div 3600) + of zz: + result.add (absOffset div 3600).intToStr(2) + of zzz: + let h = (absOffset div 3600).intToStr(2) + let m = ((absOffset div 60) mod 60).intToStr(2) + result.add h & ":" & m + of zzzz: + let absOffset = abs(dt.utcOffset) + let h = (absOffset div 3600).intToStr(2) + let m = ((absOffset div 60) mod 60).intToStr(2) + let s = (absOffset mod 60).intToStr(2) + result.add h & ":" & m & ":" & s + else: assert false + of g: + 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, + parsed: var ParsedTime): bool = + template takeInt(allowedWidth: Slice[int]): 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: + return false + i.inc pd + sv + + template contains[T](t: typedesc[T], i: int): bool = + i in low(t)..high(t) + + result = true + + case pattern + of d: + parsed.monthday = some(takeInt(1..2)) + result = parsed.monthday.get() in MonthdayRange + of dd: + parsed.monthday = some(takeInt(2..2)) + result = parsed.monthday.get() in MonthdayRange + of ddd: + result = input.substr(i, i+2).toLowerAscii() in [ + "sun", "mon", "tue", "wed", "thu", "fri", "sat"] + if result: + i.inc 3 + of dddd: + if input.substr(i, i+5).cmpIgnoreCase("sunday") == 0: + i.inc 6 + elif input.substr(i, i+5).cmpIgnoreCase("monday") == 0: + i.inc 6 + elif input.substr(i, i+6).cmpIgnoreCase("tuesday") == 0: + i.inc 7 + elif input.substr(i, i+8).cmpIgnoreCase("wednesday") == 0: + i.inc 9 + elif input.substr(i, i+7).cmpIgnoreCase("thursday") == 0: + i.inc 8 + elif input.substr(i, i+5).cmpIgnoreCase("friday") == 0: + i.inc 6 + elif input.substr(i, i+7).cmpIgnoreCase("saturday") == 0: + i.inc 8 + else: + result = false + of h, H: + parsed.hour = takeInt(1..2) + result = parsed.hour in HourRange + of hh, HH: + parsed.hour = takeInt(2..2) + result = parsed.hour in HourRange + of m: + parsed.minute = takeInt(1..2) + result = parsed.hour in MinuteRange + of mm: + parsed.minute = takeInt(2..2) + result = parsed.hour in MinuteRange + of M: + let month = takeInt(1..2) + result = month in 1..12 + parsed.month = some(month) + of MM: + let month = takeInt(2..2) + result = month in 1..12 + parsed.month = some(month) + of MMM: + case input.substr(i, i+2).toLowerAscii() + of "jan": parsed.month = some(1) + of "feb": parsed.month = some(2) + of "mar": parsed.month = some(3) + of "apr": parsed.month = some(4) + of "may": parsed.month = some(5) + of "jun": parsed.month = some(6) + of "jul": parsed.month = some(7) + of "aug": parsed.month = some(8) + of "sep": parsed.month = some(9) + of "oct": parsed.month = some(10) + of "nov": parsed.month = some(11) + of "dec": parsed.month = some(12) + else: + result = false + if result: + i.inc 3 + of MMMM: + if input.substr(i, i+6).cmpIgnoreCase("january") == 0: + parsed.month = some(1) + i.inc 7 + elif input.substr(i, i+7).cmpIgnoreCase("february") == 0: + parsed.month = some(2) + i.inc 8 + elif input.substr(i, i+4).cmpIgnoreCase("march") == 0: + parsed.month = some(3) + i.inc 5 + elif input.substr(i, i+4).cmpIgnoreCase("april") == 0: + parsed.month = some(4) + i.inc 5 + elif input.substr(i, i+2).cmpIgnoreCase("may") == 0: + parsed.month = some(5) + i.inc 3 + elif input.substr(i, i+3).cmpIgnoreCase("june") == 0: + parsed.month = some(6) + i.inc 4 + elif input.substr(i, i+3).cmpIgnoreCase("july") == 0: + parsed.month = some(7) + i.inc 4 + elif input.substr(i, i+5).cmpIgnoreCase("august") == 0: + parsed.month = some(8) + i.inc 6 + elif input.substr(i, i+8).cmpIgnoreCase("september") == 0: + parsed.month = some(9) + i.inc 9 + elif input.substr(i, i+6).cmpIgnoreCase("october") == 0: + parsed.month = some(10) + i.inc 7 + elif input.substr(i, i+7).cmpIgnoreCase("november") == 0: + parsed.month = some(11) + i.inc 8 + elif input.substr(i, i+7).cmpIgnoreCase("december") == 0: + parsed.month = some(12) + i.inc 8 + else: + result = false + of s: + parsed.second = takeInt(1..2) + of ss: + parsed.second = takeInt(2..2) + of fff, ffffff, fffffffff: + let len = ($pattern).len + let v = takeInt(len..len) + parsed.nanosecond = v * 10^(9 - len) + result = parsed.nanosecond in NanosecondRange + of t: + case input[i]: + of 'P': + parsed.amPm = apPm + of 'A': + parsed.amPm = apAm + else: + result = false + i.inc 1 + of tt: + if input.substr(i, i+1).cmpIgnoreCase("AM") == 0: + parsed.amPm = apAM + i.inc 2 + elif input.substr(i, i+1).cmpIgnoreCase("PM") == 0: + parsed.amPm = apPm + i.inc 2 + else: + result = false + of yy: + # Assumes current century + var year = takeInt(2..2) + var thisCen = now().year div 100 + parsed.year = some(thisCen*100 + year) + result = year > 0 + of yyyy: + let year = + if input[i] in { '+', '-' }: + takeInt(4..high(int)) else: - formatToken(dt, currentF, result) - dec(i) # Move position back to re-process the character separately. - currentF = "" + takeInt(4..4) + result = year > 0 + parsed.year = some(year) + of YYYY: + let year = takeInt(1..high(int)) + parsed.year = some(year) + result = year > 0 + of uuuu: + let year = + if input[i] in { '+', '-' }: + takeInt(4..high(int)) + else: + takeInt(4..4) + parsed.year = some(year) + of UUUU: + parsed.year = some(takeInt(1..high(int))) + of z, zz, zzz, zzzz: + case input[i] + of '+', '-': + let sign = if input[i] == '-': 1 else: -1 + i.inc + var offset = 0 + case pattern + of z: + offset = takeInt(1..2) * -3600 + of zz: + offset = takeInt(2..2) * -3600 + of zzz: + offset.inc takeInt(2..2) * 3600 + if input[i] != ':': + return false + i.inc + offset.inc takeInt(2..2) * 60 + of zzzz: + offset.inc takeInt(2..2) * 3600 + if input[i] != ':': + return false + i.inc + offset.inc takeInt(2..2) * 60 + if input[i] != ':': + return false + i.inc + offset.inc takeInt(2..2) + else: assert false + parsed.utcOffset = some(offset * sign) + of 'Z': + parsed.utcOffset = some(0) + i.inc + else: + result = false + of g: + if input.substr(i, i+1).cmpIgnoreCase("BC") == 0: + parsed.era = eraBc + i.inc 2 + elif input.substr(i, i+1).cmpIgnoreCase("AD") == 0: + parsed.era = eraAd + i.inc 2 + 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 + +proc toDateTime(p: ParsedTime, zone: Timezone, f: TimeFormat, + input: string): DateTime = + var month = mJan + var year: int + var monthday: int + # `now()` is an expensive call, so we avoid it when possible + (year, month, monthday) = + if p.year.isNone or p.month.isNone or p.monthday.isNone: + let n = now() + (p.year.get(n.year), + p.month.get(n.month.int).Month, + p.monthday.get(n.monthday)) + else: + (p.year.get(), p.month.get().Month, p.monthday.get()) + + year = + case p.era + of eraUnknown: + year + of eraBc: + if year < 1: + raiseParseException(f, input, + "Expected year to be positive " & + "(use 'UUUU' or 'uuuu' for negative years).") + -year + 1 + of eraAd: + if year < 1: + raiseParseException(f, input, + "Expected year to be positive " & + "(use 'UUUU' or 'uuuu' for negative years).") + year + + let hour = + case p.amPm + of apUnknown: + p.hour + of apAm: + if p.hour notin 1..12: + raiseParseException(f, input, + "AM/PM time must be in the interval 1..12") + if p.hour == 12: 0 else: p.hour + of apPm: + if p.hour notin 1..12: + raiseParseException(f, input, + "AM/PM time must be in the interval 1..12") + if p.hour == 12: p.hour else: p.hour + 12 + let minute = p.minute + let second = p.second + let nanosecond = p.nanosecond + + if monthday > getDaysInMonth(month, year): + raiseParseException(f, input, + $year & "-" & ord(month).intToStr(2) & + "-" & $monthday & " is not a valid date") + + result = DateTime( + year: year, month: month, monthday: monthday, + hour: hour, minute: minute, second: second, nanosecond: nanosecond + ) + + if p.utcOffset.isNone: + # No timezone parsed - assume timezone is `zone` + result = initDateTime(zone.zoneInfoFromTz(result.toAdjTime), zone) + else: + # Otherwise convert to `zone` + result.utcOffset = p.utcOffset.get() + result = result.toTime.inZone(zone) - inc(i) - formatToken(dt, currentF, result) +proc format*(dt: DateTime, f: TimeFormat): string {.raises: [].} = + ## Format ``dt`` using the format specified by ``f``. + runnableExamples: + let f = initTimeFormat("yyyy-MM-dd") + let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) + doAssert "2000-01-01" == dt.format(f) + var idx = 0 + while idx <= f.patterns.high: + case f.patterns[idx].FormatPattern + of Lit: + idx.inc + let len = f.patterns[idx] + for i in 1'u8..len: + idx.inc + result.add f.patterns[idx].char + idx.inc + else: + formatPattern(dt, f.patterns[idx].FormatPattern, result = result) + idx.inc + +proc format*(dt: DateTime, f: string): string = + ## Shorthand for constructing a ``TimeFormat`` and using it to format ``dt``. + ## + ## See `Parsing and formatting dates`_ for documentation of the + ## ``format`` argument. + runnableExamples: + let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) + doAssert "2000-01-01" == format(dt, "yyyy-MM-dd") + let dtFormat = initTimeFormat(f) + result = dt.format(dtFormat) + +proc format*(dt: DateTime, f: static[string]): string {.raises: [].} = + ## Overload that validates ``format`` at compile time. + const f2 = initTimeFormat(f) + result = dt.format(f2) proc format*(time: Time, f: string, zone: Timezone = local()): string {.tags: [].} = - ## Converts a `Time` value to a string representation. It will use format from - ## ``format(dt: DateTime, f: string)``. + ## Shorthand for constructing a ``TimeFormat`` and using it to format + ## ``time``. Will use the timezone specified by ``zone``. + ## + ## See `Parsing and formatting dates`_ for documentation of the + ## ``f`` argument. runnableExamples: var dt = initDateTime(01, mJan, 1970, 00, 00, 00, utc()) var tm = dt.toTime() 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: [].} = + ## 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 = + ## 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 + ## converted to the ``zone`` timezone. + runnableExamples: + let f = initTimeFormat("yyyy-MM-dd") + let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) + doAssert dt == "2000-01-01".parse(f, utc()) + var inpIdx = 0 # Input index + var patIdx = 0 # Pattern index + var parsed: ParsedTime + while inpIdx <= input.high and patIdx <= f.patterns.high: + let pattern = f.patterns[patIdx].FormatPattern + case pattern + of Lit: + patIdx.inc + let len = f.patterns[patIdx] + patIdx.inc + for _ in 1'u8..len: + if input[inpIdx] != f.patterns[patIdx].char: + raiseParseException(f, input, + "Unexpected character: " & input[inpIdx]) + inpIdx.inc + patIdx.inc + else: + if not parsePattern(input, pattern, inpIdx, parsed): + raiseParseException(f, input, &"Failed on pattern '{pattern}'") + patIdx.inc + + if inpIdx <= input.high: + raiseParseException(f, input, + "Parsing ended but there was still input remaining") + + if patIdx <= f.patterns.high: + raiseParseException(f, input, + "Parsing ended but there was still patterns remaining") + + result = toDateTime(parsed, zone, f, input) + +proc parse*(input, f: string, tz: Timezone = local()): DateTime = + ## Shorthand for constructing a ``TimeFormat`` and using it to parse + ## ``input`` as a ``DateTime``. + ## + ## See `Parsing and formatting dates`_ for documentation of the + ## ``f`` argument. + runnableExamples: + let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) + doAssert dt == parse("2000-01-01", "yyyy-MM-dd", utc()) + let dtFormat = initTimeFormat(f) + result = input.parse(dtFormat, tz) + +proc parse*(input: string, f: static[string], zone: Timezone = local()): DateTime = + ## Overload that validates ``f`` at compile time. + const f2 = initTimeFormat(f) + result = input.parse(f2, zone) + +proc parseTime*(input, f: string, zone: Timezone): Time = + ## Shorthand for constructing a ``TimeFormat`` and using it to parse + ## ``input`` as a ``DateTime``, then converting it a ``Time``. + ## + ## See `Parsing and formatting dates`_ for documentation of the + ## ``format`` argument. + runnableExamples: + let tStr = "1970-01-01T00:00:00+00:00" + 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 = + ## Overload that validates ``format`` at compile time. + const f2 = initTimeFormat(f) + result = input.parse(f2, zone).toTime() + +# +# End of parse & format implementation +# + proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} = ## Converts a `DateTime` object to a string representation. ## It uses the format ``yyyy-MM-dd'T'HH-mm-sszzz``. runnableExamples: let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc()) - doAssert $dt == "2000-01-01T12:00:00+00:00" - try: - result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz") # todo: optimize this - except ValueError: assert false # cannot happen because format string is valid + doAssert $dt == "2000-01-01T12:00:00Z" + 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 @@ -1628,328 +2218,6 @@ proc `$`*(time: Time): string {.tags: [], raises: [], benign.} = {.pop.} -proc parseToken(dt: var DateTime; token, value: string; j: var int) = - ## Helper of the parse proc to parse individual tokens. - - # Overwrite system.`[]` to raise a ValueError on index out of bounds. - proc `[]`[T, U](s: string, x: HSlice[T, U]): string = - if x.a >= s.len or x.b >= s.len: - raise newException(ValueError, "Value is missing required tokens, got: " & - s) - return system.`[]`(s, x) - - var sv: int - case token - of "d": - var pd = parseInt(value[j..j+1], sv) - dt.monthday = sv - j += pd - of "dd": - dt.monthday = value[j..j+1].parseInt() - j += 2 - of "ddd": - case value[j..j+2].toLowerAscii() - of "sun": dt.weekday = dSun - of "mon": dt.weekday = dMon - of "tue": dt.weekday = dTue - of "wed": dt.weekday = dWed - of "thu": dt.weekday = dThu - of "fri": dt.weekday = dFri - of "sat": dt.weekday = dSat - else: - raise newException(ValueError, - "Couldn't parse day of week (ddd), got: " & value[j..j+2]) - j += 3 - of "dddd": - if value.len >= j+6 and value[j..j+5].cmpIgnoreCase("sunday") == 0: - dt.weekday = dSun - j += 6 - elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("monday") == 0: - dt.weekday = dMon - j += 6 - elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("tuesday") == 0: - dt.weekday = dTue - j += 7 - elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("wednesday") == 0: - dt.weekday = dWed - j += 9 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("thursday") == 0: - dt.weekday = dThu - j += 8 - elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("friday") == 0: - dt.weekday = dFri - j += 6 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("saturday") == 0: - dt.weekday = dSat - j += 8 - else: - raise newException(ValueError, - "Couldn't parse day of week (dddd), got: " & value) - of "h", "H": - var pd = parseInt(value[j..j+1], sv) - dt.hour = sv - j += pd - of "hh", "HH": - dt.hour = value[j..j+1].parseInt() - j += 2 - of "m": - var pd = parseInt(value[j..j+1], sv) - dt.minute = sv - j += pd - of "mm": - dt.minute = value[j..j+1].parseInt() - j += 2 - of "M": - var pd = parseInt(value[j..j+1], sv) - dt.month = sv.Month - j += pd - of "MM": - var month = value[j..j+1].parseInt() - j += 2 - dt.month = month.Month - of "MMM": - case value[j..j+2].toLowerAscii(): - of "jan": dt.month = mJan - of "feb": dt.month = mFeb - of "mar": dt.month = mMar - of "apr": dt.month = mApr - of "may": dt.month = mMay - of "jun": dt.month = mJun - of "jul": dt.month = mJul - of "aug": dt.month = mAug - of "sep": dt.month = mSep - of "oct": dt.month = mOct - of "nov": dt.month = mNov - of "dec": dt.month = mDec - else: - raise newException(ValueError, - "Couldn't parse month (MMM), got: " & value) - j += 3 - of "MMMM": - if value.len >= j+7 and value[j..j+6].cmpIgnoreCase("january") == 0: - dt.month = mJan - j += 7 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("february") == 0: - dt.month = mFeb - j += 8 - elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("march") == 0: - dt.month = mMar - j += 5 - elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("april") == 0: - dt.month = mApr - j += 5 - elif value.len >= j+3 and value[j..j+2].cmpIgnoreCase("may") == 0: - dt.month = mMay - j += 3 - elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("june") == 0: - dt.month = mJun - j += 4 - elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("july") == 0: - dt.month = mJul - j += 4 - elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("august") == 0: - dt.month = mAug - j += 6 - elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("september") == 0: - dt.month = mSep - j += 9 - elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("october") == 0: - dt.month = mOct - j += 7 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("november") == 0: - dt.month = mNov - j += 8 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("december") == 0: - dt.month = mDec - j += 8 - else: - raise newException(ValueError, - "Couldn't parse month (MMMM), got: " & value) - of "s": - var pd = parseInt(value[j..j+1], sv) - dt.second = sv - j += pd - of "ss": - dt.second = value[j..j+1].parseInt() - j += 2 - of "t": - if value[j] == 'A' and dt.hour == 12: - dt.hour = 0 - elif value[j] == 'P' and dt.hour > 0 and dt.hour < 12: - dt.hour += 12 - j += 1 - of "tt": - if value[j..j+1] == "AM" and dt.hour == 12: - dt.hour = 0 - elif value[j..j+1] == "PM" and dt.hour > 0 and dt.hour < 12: - dt.hour += 12 - j += 2 - of "yy": - # Assumes current century - var year = value[j..j+1].parseInt() - var thisCen = now().year div 100 - dt.year = thisCen*100 + year - j += 2 - of "yyyy": - dt.year = value[j..j+3].parseInt() - j += 4 - of "z": - dt.isDst = false - let ch = if j < value.len: value[j] else: '\0' - if ch == '+': - dt.utcOffset = 0 - parseInt($value[j+1]) * secondsInHour - elif ch == '-': - dt.utcOffset = parseInt($value[j+1]) * secondsInHour - elif ch == 'Z': - dt.utcOffset = 0 - j += 1 - return - else: - raise newException(ValueError, - "Couldn't parse timezone offset (z), got: " & ch) - j += 2 - of "zz": - dt.isDst = false - let ch = if j < value.len: value[j] else: '\0' - if ch == '+': - dt.utcOffset = 0 - value[j+1..j+2].parseInt() * secondsInHour - elif ch == '-': - dt.utcOffset = value[j+1..j+2].parseInt() * secondsInHour - elif ch == 'Z': - dt.utcOffset = 0 - j += 1 - return - else: - raise newException(ValueError, - "Couldn't parse timezone offset (zz), got: " & ch) - j += 3 - of "zzz": - dt.isDst = false - var factor = 0 - let ch = if j < value.len: value[j] else: '\0' - if ch == '+': factor = -1 - elif ch == '-': factor = 1 - elif ch == 'Z': - dt.utcOffset = 0 - j += 1 - return - else: - raise newException(ValueError, - "Couldn't parse timezone offset (zzz), got: " & ch) - dt.utcOffset = factor * value[j+1..j+2].parseInt() * secondsInHour - j += 4 - dt.utcOffset += factor * value[j..j+1].parseInt() * 60 - j += 2 - of "fff", "ffffff", "fffffffff": - var numStr = "" - let n = parseWhile(value[j..len(value) - 1], numStr, {'0'..'9'}) - dt.nanosecond = parseInt(numStr) * (10 ^ (9 - n)) - j += n - else: - # Ignore the token and move forward in the value string by the same length - j += token.len - -proc parse*(value, layout: string, zone: Timezone = local()): DateTime = - ## This procedure parses a date/time string using the standard format - ## identifiers as listed below. The procedure defaults information not provided - ## in the format string from the running program (month, year, etc). - ## - ## The return value will always be in the `zone` timezone. If no UTC offset was - ## parsed, then the input will be assumed to be specified in the `zone` timezone - ## already, so no timezone conversion will be done in that case. - ## - ## ======================= ================================================================================= ================================================ - ## Specifier Description Example - ## ======================= ================================================================================= ================================================ - ## d Numeric value of the day of the month, it will be one or two digits long. ``1/04/2012 -> 1``, ``21/04/2012 -> 21`` - ## dd Same as above, but always two digits. ``1/04/2012 -> 01``, ``21/04/2012 -> 21`` - ## ddd Three letter string which indicates the day of the week. ``Saturday -> Sat``, ``Monday -> Mon`` - ## dddd Full string for the day of the week. ``Saturday -> Saturday``, ``Monday -> Monday`` - ## h The hours in one digit if possible. Ranging from 0-12. ``5pm -> 5``, ``2am -> 2`` - ## hh The hours in two digits always. If the hour is one digit 0 is prepended. ``5pm -> 05``, ``11am -> 11`` - ## H The hours in one digit if possible, randing from 0-24. ``5pm -> 17``, ``2am -> 2`` - ## HH The hours in two digits always. 0 is prepended if the hour is one digit. ``5pm -> 17``, ``2am -> 02`` - ## m The minutes in 1 digit if possible. ``5:30 -> 30``, ``2:01 -> 1`` - ## mm Same as above but always 2 digits, 0 is prepended if the minute is one digit. ``5:30 -> 30``, ``2:01 -> 01`` - ## M The month in one digit if possible. ``September -> 9``, ``December -> 12`` - ## MM The month in two digits always. 0 is prepended. ``September -> 09``, ``December -> 12`` - ## MMM Abbreviated three-letter form of the month. ``September -> Sep``, ``December -> Dec`` - ## MMMM Full month string, properly capitalized. ``September -> September`` - ## s Seconds as one digit if possible. ``00:00:06 -> 6`` - ## ss Same as above but always two digits. 0 is prepended. ``00:00:06 -> 06`` - ## t ``A`` when time is in the AM. ``P`` when time is in the PM. - ## tt Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively. - ## yy Displays the year to two digits. ``2012 -> 12`` - ## yyyy Displays the year to four digits. ``2012 -> 2012`` - ## z Displays the timezone offset from UTC. ``Z`` is parsed as ``+0`` ``GMT+7 -> +7``, ``GMT-5 -> -5`` - ## zz Same as above but with leading 0. ``GMT+7 -> +07``, ``GMT-5 -> -05`` - ## zzz Same as above but with ``:mm`` where *mm* represents minutes. ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00`` - ## fff/ffffff/fffffffff for consistency with format - nanoseconds ``1 -> 1 nanosecond`` - ## ======================= ================================================================================= ================================================ - ## - ## Other strings can be inserted by putting them in ``''``. For example - ## ``hh'->'mm`` will give ``01->56``. The following characters can be - ## inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` - ## ``,``. However you don't need to necessarily separate format specifiers, a - ## unambiguous format string like ``yyyyMMddhhmmss`` is valid too. - runnableExamples: - let tStr = "1970-01-01T00:00:00.0+00:00" - doAssert parse(tStr, "yyyy-MM-dd'T'HH:mm:ss.fffzzz") == fromUnix(0).utc - - var i = 0 # pointer for format string - var j = 0 # pointer for value string - var token = "" - # Assumes current day of month, month and year, but time is reset to 00:00:00. Weekday will be reset after parsing. - var dt = now() - dt.hour = 0 - dt.minute = 0 - dt.second = 0 - dt.nanosecond = 0 - dt.isDst = true # using this is flag for checking whether a timezone has \ - # been read (because DST is always false when a tz is parsed) - while i < layout.len: - case layout[i] - of ' ', '-', '/', ':', '\'', '(', ')', '[', ']', ',': - if token.len > 0: - parseToken(dt, token, value, j) - # Reset token - token = "" - # Skip separator and everything between single quotes - # These are literals in both the layout and the value string - if layout[i] == '\'': - inc(i) - while i < layout.len-1 and layout[i] != '\'': - inc(i) - inc(j) - inc(i) - else: - inc(i) - inc(j) - else: - # Check if the letter being added matches previous accumulated buffer. - if token.len == 0 or token[high(token)] == layout[i]: - token.add(layout[i]) - inc(i) - else: - parseToken(dt, token, value, j) - token = "" - - if i >= layout.len and token.len > 0: - parseToken(dt, token, value, j) - if dt.isDst: - # No timezone parsed - assume timezone is `zone` - result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone) - else: - # Otherwise convert to `zone` - result = dt.toTime.inZone(zone) - -proc parseTime*(value, layout: string, zone: Timezone): Time = - ## Simple wrapper for parsing string to time - runnableExamples: - let tStr = "1970-01-01T00:00:00+00:00" - doAssert parseTime(tStr, "yyyy-MM-dd'T'HH:mm:sszzz", local()) == fromUnix(0) - parse(value, layout, zone).toTime() - proc countLeapYears*(yearSpan: int): int = ## Returns the number of leap years spanned by a given number of years. ## diff --git a/lib/system.nim b/lib/system.nim index 2f6f45948..fee3d4966 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -275,14 +275,14 @@ proc high*[T: Ordinal](x: T): T {.magic: "High", noSideEffect.} ## high(2) #=> 9223372036854775807 ## high(int) #=> 9223372036854775807 -proc high*[T: Ordinal](x: typeDesc[T]): T {.magic: "High", noSideEffect.} +proc high*[T: Ordinal|enum](x: typeDesc[T]): T {.magic: "High", noSideEffect.} proc high*[T](x: openArray[T]): int {.magic: "High", noSideEffect.} proc high*[I, T](x: array[I, T]): I {.magic: "High", noSideEffect.} proc high*[I, T](x: typeDesc[array[I, T]]): I {.magic: "High", noSideEffect.} proc high*(x: cstring): int {.magic: "High", noSideEffect.} proc high*(x: string): int {.magic: "High", noSideEffect.} -proc low*[T: Ordinal](x: typeDesc[T]): T {.magic: "Low", noSideEffect.} +proc low*[T: Ordinal|enum](x: typeDesc[T]): T {.magic: "Low", noSideEffect.} proc low*[T](x: openArray[T]): int {.magic: "Low", noSideEffect.} proc low*[I, T](x: array[I, T]): I {.magic: "Low", noSideEffect.} proc low*[T](x: T): T {.magic: "Low", noSideEffect.} @@ -806,7 +806,7 @@ proc card*[T](x: set[T]): int {.magic: "Card", noSideEffect.} ## var i = {1,2,3,4} ## card(i) #=> 4 -proc ord*[T](x: T): int {.magic: "Ord", noSideEffect.} +proc ord*[T: Ordinal|enum](x: T): int {.magic: "Ord", noSideEffect.} ## returns the internal int value of an ordinal value ``x``. ## ## .. code-block:: nim @@ -2959,6 +2959,22 @@ when not defined(JS): #and not defined(nimscript): ## useful for low-level file access include "system/ansi_c" + include "system/memory" + + proc zeroMem(p: pointer, size: Natural) = + nimZeroMem(p, size) + when declared(memTrackerOp): + memTrackerOp("zeroMem", p, size) + proc copyMem(dest, source: pointer, size: Natural) = + nimCopyMem(dest, source, size) + when declared(memTrackerOp): + memTrackerOp("copyMem", dest, size) + proc moveMem(dest, source: pointer, size: Natural) = + c_memmove(dest, source, size) + when declared(memTrackerOp): + memTrackerOp("moveMem", dest, size) + proc equalMem(a, b: pointer, size: Natural): bool = + nimCmpMem(a, b, size) == 0 proc cmp(x, y: string): int = when nimvm: @@ -2967,7 +2983,7 @@ when not defined(JS): #and not defined(nimscript): else: result = 0 else: let minlen = min(x.len, y.len) - result = int(c_memcmp(x.cstring, y.cstring, minlen.csize)) + result = int(nimCmpMem(x.cstring, y.cstring, minlen.csize)) if result == 0: result = x.len - y.len @@ -3256,22 +3272,6 @@ when not defined(JS): #and not defined(nimscript): when defined(memtracker): include "system/memtracker" - when not defined(nimscript): - proc zeroMem(p: pointer, size: Natural) = - c_memset(p, 0, size) - when declared(memTrackerOp): - memTrackerOp("zeroMem", p, size) - proc copyMem(dest, source: pointer, size: Natural) = - c_memcpy(dest, source, size) - when declared(memTrackerOp): - memTrackerOp("copyMem", dest, size) - proc moveMem(dest, source: pointer, size: Natural) = - c_memmove(dest, source, size) - when declared(memTrackerOp): - memTrackerOp("moveMem", dest, size) - proc equalMem(a, b: pointer, size: Natural): bool = - c_memcmp(a, b, size) == 0 - when hostOS == "standalone": include "system/embedded" else: @@ -4154,11 +4154,10 @@ template doAssertRaises*(exception, code: untyped): typed = ## .. code-block:: nim ## doAssertRaises(ValueError): ## raise newException(ValueError, "Hello World") - # TODO: investigate why runnableExamples here caused - # https://github.com/nim-lang/Nim/issues/8223 var wrong = false try: - code + if true: + code wrong = true except exception: discard diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index 6aef4f411..95becde22 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -830,7 +830,7 @@ proc rawDealloc(a: var MemRegion, p: pointer) = c.freeList = f when overwriteFree: # set to 0xff to check for usage after free bugs: - c_memset(cast[pointer](cast[int](p) +% sizeof(FreeCell)), -1'i32, + nimSetMem(cast[pointer](cast[int](p) +% sizeof(FreeCell)), -1'i32, s -% sizeof(FreeCell)) # check if it is not in the freeSmallChunks[s] list: if c.free < s: @@ -847,7 +847,7 @@ proc rawDealloc(a: var MemRegion, p: pointer) = s == 0, "rawDealloc 2") else: # set to 0xff to check for usage after free bugs: - when overwriteFree: c_memset(p, -1'i32, c.size -% bigChunkOverhead()) + when overwriteFree: nimSetMem(p, -1'i32, c.size -% bigChunkOverhead()) # free big chunk var c = cast[PBigChunk](c) dec a.occ, c.size diff --git a/lib/system/ansi_c.nim b/lib/system/ansi_c.nim index 52cb15e39..4d21f8747 100644 --- a/lib/system/ansi_c.nim +++ b/lib/system/ansi_c.nim @@ -25,6 +25,8 @@ proc c_memset(p: pointer, value: cint, size: csize): pointer {. importc: "memset", header: "<string.h>", discardable.} proc c_strcmp(a, b: cstring): cint {. importc: "strcmp", header: "<string.h>", noSideEffect.} +proc c_strlen(a: cstring): csize {. + importc: "strlen", header: "<string.h>", noSideEffect.} when defined(linux) and defined(amd64): type diff --git a/lib/system/memory.nim b/lib/system/memory.nim new file mode 100644 index 000000000..f86fd4696 --- /dev/null +++ b/lib/system/memory.nim @@ -0,0 +1,47 @@ +const useLibC = not defined(nimNoLibc) + +proc nimCopyMem(dest, source: pointer, size: Natural) {.compilerproc, inline.} = + when useLibC: + c_memcpy(dest, source, size) + else: + let d = cast[ptr UncheckedArray[byte]](dest) + let s = cast[ptr UncheckedArray[byte]](source) + var i = 0 + while i < size: + d[i] = s[i] + inc i + +proc nimSetMem(a: pointer, v: cint, size: Natural) {.inline.} = + when useLibC: + c_memset(a, v, size) + else: + let a = cast[ptr UncheckedArray[byte]](a) + var i = 0 + let v = cast[byte](v) + while i < size: + a[i] = v + inc i + +proc nimZeroMem(p: pointer, size: Natural) {.compilerproc, inline.} = + nimSetMem(p, 0, size) + +proc nimCmpMem(a, b: pointer, size: Natural): cint {.compilerproc, inline.} = + when useLibC: + c_memcmp(a, b, size) + else: + let a = cast[ptr UncheckedArray[byte]](a) + let b = cast[ptr UncheckedArray[byte]](b) + var i = 0 + while i < size: + let d = a[i].cint - b[i].cint + if d != 0: return d + inc i + +proc nimCStrLen(a: cstring): csize {.compilerproc, inline.} = + when useLibC: + c_strlen(a) + else: + var a = cast[ptr byte](a) + while a[] != 0: + a = cast[ptr byte](cast[uint](a) + 1) + inc result diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim index f6e691c0d..e6b8ec3a6 100644 --- a/lib/system/sysio.nim +++ b/lib/system/sysio.nim @@ -154,7 +154,7 @@ proc readLine(f: File, line: var TaintedString): bool = while true: # memset to \L so that we can tell how far fgets wrote, even on EOF, where # fgets doesn't append an \L - c_memset(addr line.string[pos], '\L'.ord, sp) + nimSetMem(addr line.string[pos], '\L'.ord, sp) var fgetsSuccess = c_fgets(addr line.string[pos], sp, f) != nil if not fgetsSuccess: checkErr(f) let m = c_memchr(addr line.string[pos], '\L'.ord, sp) diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim index de3bfa616..47fff8397 100644 --- a/lib/wrappers/openssl.nim +++ b/lib/wrappers/openssl.nim @@ -327,6 +327,7 @@ proc ERR_load_BIO_strings*(){.cdecl, dynlib: DLLUtilName, importc.} proc SSL_new*(context: SslCtx): SslPtr{.cdecl, dynlib: DLLSSLName, importc.} proc SSL_free*(ssl: SslPtr){.cdecl, dynlib: DLLSSLName, importc.} proc SSL_get_SSL_CTX*(ssl: SslPtr): SslCtx {.cdecl, dynlib: DLLSSLName, importc.} +proc SSL_set_SSL_CTX*(ssl: SslPtr, ctx: SslCtx): SslCtx {.cdecl, dynlib: DLLSSLName, importc.} proc SSL_CTX_new*(meth: PSSL_METHOD): SslCtx{.cdecl, dynlib: DLLSSLName, importc.} proc SSL_CTX_load_verify_locations*(ctx: SslCtx, CAfile: cstring, diff --git a/tests/casestmt/t8333.nim b/tests/casestmt/t8333.nim new file mode 100644 index 000000000..ca3523358 --- /dev/null +++ b/tests/casestmt/t8333.nim @@ -0,0 +1,10 @@ +discard """ + output: "1" +""" + +converter toInt*(x: char): int = + x.int + +case 0 +of 'a': echo 0 +else: echo 1 diff --git a/tests/concepts/t8280.nim b/tests/concepts/t8280.nim new file mode 100644 index 000000000..ba33af34e --- /dev/null +++ b/tests/concepts/t8280.nim @@ -0,0 +1,16 @@ +discard """ + output: "()" +""" + +type + Iterable[T] = concept x + for elem in x: + elem is T + +proc max[A](iter: Iterable[A]): A = + discard + +type + MyType = object + +echo max(@[MyType()]) diff --git a/tests/converter/t7098.nim b/tests/converter/t7098.nim new file mode 100644 index 000000000..66e629fa8 --- /dev/null +++ b/tests/converter/t7098.nim @@ -0,0 +1,31 @@ +type + Byte* = uint8 + Bytes* = seq[Byte] + + BytesRange* = object + bytes: Bytes + ibegin, iend: int + +proc initBytesRange*(s: var Bytes, ibegin = 0, iend = -1): BytesRange = + let e = if iend < 0: s.len + iend + 1 + else: iend + assert ibegin >= 0 and e <= s.len + + shallow(s) + result.bytes = s + result.ibegin = ibegin + result.iend = e + +converter fromSeq*(s: Bytes): BytesRange = + var seqCopy = s + return initBytesRange(seqCopy) + +type + Reader* = object + data: BytesRange + position: int + +proc readerFromBytes*(input: BytesRange): Reader = + discard + +let r = readerFromBytes(@[]) diff --git a/tests/generics/t7794.nim b/tests/generics/t7794.nim new file mode 100644 index 000000000..b295da865 --- /dev/null +++ b/tests/generics/t7794.nim @@ -0,0 +1,15 @@ +discard """ +output: ''' +10 +2.0 +''' +""" + +type + Data*[T:SomeNumber, U:SomeReal] = ref object + x*: T + value*: U + +var d = Data[int, float64](x:10.int, value:2'f64) +echo d.x +echo d.value diff --git a/tests/generics/t8270.nim b/tests/generics/t8270.nim new file mode 100644 index 000000000..707e981fa --- /dev/null +++ b/tests/generics/t8270.nim @@ -0,0 +1,7 @@ +discard """ + line: 6 + errormsg: "cannot instantiate: \'T\'" +""" + +proc m[T](x: T): int = discard +echo [m] diff --git a/tests/init/t8314.nim b/tests/init/t8314.nim new file mode 100644 index 000000000..59d46eb33 --- /dev/null +++ b/tests/init/t8314.nim @@ -0,0 +1,21 @@ +discard """ + nimout: ''' +t8314.nim(8, 7) Hint: BEGIN [User] +t8314.nim(19, 7) Hint: END [User] + ''' +""" + +{.hint: "BEGIN".} +proc foo(x: range[1..10]) = + block: + var (y,) = (x,) + echo y + block: + var (_,y) = (1,x) + echo y + block: + var (y,_,) = (x,1,) + echo y +{.hint: "END".} + +foo(1) diff --git a/tests/js/ttimes.nim b/tests/js/ttimes.nim index 63972dd76..bd599a7ae 100644 --- a/tests/js/ttimes.nim +++ b/tests/js/ttimes.nim @@ -36,8 +36,8 @@ let utcPlus2 = Timezone(zoneInfoFromUtc: staticZoneInfoFromUtc, zoneInfoFromTz: block timezoneTests: let dt = initDateTime(01, mJan, 2017, 12, 00, 00, utcPlus2) doAssert $dt == "2017-01-01T12:00:00+02:00" - doAssert $dt.utc == "2017-01-01T10:00:00+00:00" + doAssert $dt.utc == "2017-01-01T10:00:00Z" doAssert $dt.utc.inZone(utcPlus2) == $dt -doAssert $initDateTime(01, mJan, 1911, 12, 00, 00, utc()) == "1911-01-01T12:00:00+00:00" -doAssert $initDateTime(01, mJan, 0023, 12, 00, 00, utc()) == "0023-01-01T12:00:00+00:00" \ No newline at end of file +doAssert $initDateTime(01, mJan, 1911, 12, 00, 00, utc()) == "1911-01-01T12:00:00Z" +doAssert $initDateTime(01, mJan, 0023, 12, 00, 00, utc()) == "0023-01-01T12:00:00Z" \ No newline at end of file diff --git a/tests/macros/tmacrostmt.nim b/tests/macros/tmacrostmt.nim index 849a32ea3..a6e1e66dd 100644 --- a/tests/macros/tmacrostmt.nim +++ b/tests/macros/tmacrostmt.nim @@ -24,3 +24,23 @@ macro foo: typed = else: echo "Does not compute! (test OK)" foo() + +#------------------------------------ +# bug #8287 +type MyString = distinct string + +proc `$` (c: MyString): string {.borrow.} + +proc `!!` (c: cstring): int = + c.len + +proc f(name: MyString): int = + !! $ name + +macro repr_and_parse(fn: typed): typed = + let fn_impl = fn.getImpl + fn_impl.name = genSym(nskProc, $fn_impl.name) + echo fn_impl.repr + result = parseStmt(fn_impl.repr) + +repr_and_parse(f) \ No newline at end of file diff --git a/tests/macros/typesafeprintf.nim b/tests/macros/typesafeprintf.nim index 2f4622f3e..daf213bd3 100644 --- a/tests/macros/typesafeprintf.nim +++ b/tests/macros/typesafeprintf.nim @@ -9,7 +9,7 @@ proc printfImpl(formatstr: cstring) {.importc: "printf", varargs.} iterator tokenize(format: string): char = var i = 0 - while true: + while i < format.len: case format[i] of '%': case format[i+1] @@ -42,7 +42,8 @@ macro printf(formatString: string{lit}, args: varargs[typed]): untyped = $expectedType & ", actual type: " & $actualType # keep the original callsite, but use cprintf instead - result = callsite() - result[0] = bindSym"printfImpl" + result = newCall(bindSym"printfImpl") + result.add formatString + for a in args: result.add a printf("test %d\n", 10) diff --git a/tests/misc/åäö.nim b/tests/misc/åäö.nim index 69bb3e22c..b3caa9861 100644 --- a/tests/misc/åäö.nim +++ b/tests/misc/åäö.nim @@ -5,4 +5,7 @@ discard """ # Tests that module names can contain multi byte characters let a = 1 -doAssert åäö.a == 1 \ No newline at end of file +doAssert åäö.a == 1 + +proc inlined() {.inline.} = discard +inlined() \ No newline at end of file diff --git a/tests/pragmas/tcustom_pragma.nim b/tests/pragmas/tcustom_pragma.nim index cd1edccf6..a5f86b54d 100644 --- a/tests/pragmas/tcustom_pragma.nim +++ b/tests/pragmas/tcustom_pragma.nim @@ -145,4 +145,12 @@ block: type Annotated {.simpleAttr.} = object proc generic_proc[T]() = - assert Annotated.hasCustomPragma(simpleAttr) \ No newline at end of file + assert Annotated.hasCustomPragma(simpleAttr) + + +#-------------------------------------------------------------------------- +# Pragma on proc type + +let a: proc(x: int) {.defaultValue(5).} = nil +static: + doAssert hasCustomPragma(a.type, defaultValue) diff --git a/tests/stdlib/ttimes.nim b/tests/stdlib/ttimes.nim index 4ab3ba581..e3f61ff77 100644 --- a/tests/stdlib/ttimes.nim +++ b/tests/stdlib/ttimes.nim @@ -8,6 +8,24 @@ discard """ import times, os, strutils, unittest +proc staticTz(hours, minutes, seconds: int = 0): Timezone {.noSideEffect.} = + let offset = hours * 3600 + minutes * 60 + seconds + + proc zoneInfoFromTz(adjTime: Time): ZonedTime {.locks: 0.} = + result.isDst = false + result.utcOffset = offset + result.adjTime = adjTime + + proc zoneInfoFromUtc(time: Time): ZonedTime {.locks: 0.}= + result.isDst = false + result.utcOffset = offset + result.adjTime = fromUnix(time.toUnix - offset) + + result.name = "" + result.zoneInfoFromTz = zoneInfoFromTz + result.zoneInfoFromUtc = zoneInfoFromUtc + + # $ date --date='@2147483647' # Tue 19 Jan 03:14:07 GMT 2038 @@ -19,25 +37,10 @@ proc checkFormat(t: DateTime, format, expected: string) = echo "actual : ", actual doAssert false -let t = fromUnix(2147483647).utc -t.checkFormat("ddd dd MMM hh:mm:ss yyyy", "Tue 19 Jan 03:14:07 2038") -t.checkFormat("ddd ddMMMhh:mm:ssyyyy", "Tue 19Jan03:14:072038") -t.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & - " ss t tt y yy yyy yyyy yyyyy z zz zzz", - "19 19 Tue Tuesday 3 03 3 03 14 14 1 01 Jan January 7 07 A AM 8 38 038 2038 02038 +0 +00 +00:00") - -t.checkFormat("yyyyMMddhhmmss", "20380119031407") - -# issue 7620 -let t7620_am = parse("4/15/2017 12:01:02 AM +0", "M/d/yyyy' 'h:mm:ss' 'tt' 'z", utc()) -t7620_am.checkFormat("M/d/yyyy' 'h:mm:ss' 'tt' 'z", "4/15/2017 12:01:02 AM +0") -let t7620_pm = parse("4/15/2017 12:01:02 PM +0", "M/d/yyyy' 'h:mm:ss' 'tt' 'z", utc()) -t7620_pm.checkFormat("M/d/yyyy' 'h:mm:ss' 'tt' 'z", "4/15/2017 12:01:02 PM +0") - -let t2 = fromUnix(160070789).utc # Mon 27 Jan 16:06:29 GMT 1975 +let t2 = fromUnix(160070789).utc() # Mon 27 Jan 16:06:29 GMT 1975 t2.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & " ss t tt y yy yyy yyyy yyyyy z zz zzz", - "27 27 Mon Monday 4 04 16 16 6 06 1 01 Jan January 29 29 P PM 5 75 975 1975 01975 +0 +00 +00:00") + "27 27 Mon Monday 4 04 16 16 6 06 1 01 Jan January 29 29 P PM 5 75 975 1975 01975 Z Z Z") var t4 = fromUnix(876124714).utc # Mon 6 Oct 08:58:34 BST 1997 t4.checkFormat("M MM MMM MMMM", "10 10 Oct October") @@ -83,16 +86,16 @@ let seqB: seq[Time] = @[] doAssert seqA == seqB for tz in [ - (0, "+0", "+00", "+00:00"), # UTC - (-3600, "+1", "+01", "+01:00"), # CET - (-39600, "+11", "+11", "+11:00"), # two digits - (-1800, "+0", "+00", "+00:30"), # half an hour - (7200, "-2", "-02", "-02:00"), # positive - (38700, "-10", "-10", "-10:45")]: # positive with three quaters hour - let ti = DateTime(month: mJan, monthday: 1, utcOffset: tz[0]) - doAssert ti.format("z") == tz[1] - doAssert ti.format("zz") == tz[2] - doAssert ti.format("zzz") == tz[3] + (staticTz(seconds = 0), "+0", "+00", "+00:00"), # UTC + (staticTz(seconds = -3600), "+1", "+01", "+01:00"), # CET + (staticTz(seconds = -39600), "+11", "+11", "+11:00"), # two digits + (staticTz(seconds = -1800), "+0", "+00", "+00:30"), # half an hour + (staticTz(seconds = 7200), "-2", "-02", "-02:00"), # positive + (staticTz(seconds = 38700), "-10", "-10", "-10:45")]: # positive with three quaters hour + let dt = initDateTime(1, mJan, 2000, 00, 00, 00, tz[0]) + doAssert dt.format("z") == tz[1] + doAssert dt.format("zz") == tz[2] + doAssert dt.format("zzz") == tz[3] block countLeapYears: # 1920, 2004 and 2020 are leap years, and should be counted starting at the following year @@ -112,11 +115,9 @@ template parseTest(s, f, sExpected: string, ydExpected: int) = let parsed = s.parse(f, utc()) parsedStr = $parsed + if parsedStr != sExpected: + echo "GOT ", parsedStr, " EXPECTED ", sExpected, " FORMAT ", f check parsedStr == sExpected - if parsed.yearday != ydExpected: - echo s - echo parsed.repr - echo parsed.yearday, " exp: ", ydExpected check(parsed.yearday == ydExpected) template parseTestExcp(s, f: string) = @@ -130,51 +131,43 @@ template parseTestTimeOnly(s, f, sExpected: string) = # explicit timezone offsets in all tests. template runTimezoneTests() = parseTest("Tuesday at 09:04am on Dec 15, 2015 +0", - "dddd at hh:mmtt on MMM d, yyyy z", "2015-12-15T09:04:00+00:00", 348) + "dddd 'at' hh:mmtt 'on' MMM d, yyyy z", "2015-12-15T09:04:00Z", 348) # ANSIC = "Mon Jan _2 15:04:05 2006" parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z", - "2006-01-12T15:04:05+00:00", 11) + "2006-01-12T15:04:05Z", 11) # UnixDate = "Mon Jan _2 15:04:05 MST 2006" parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z", - "2006-01-12T15:04:05+00:00", 11) + "2006-01-12T15:04:05Z", 11) # RubyDate = "Mon Jan 02 15:04:05 -0700 2006" parseTest("Mon Feb 29 15:04:05 -07:00 2016 +0", "ddd MMM dd HH:mm:ss zzz yyyy z", - "2016-02-29T15:04:05+00:00", 59) # leap day + "2016-02-29T15:04:05Z", 59) # leap day # RFC822 = "02 Jan 06 15:04 MST" parseTest("12 Jan 16 15:04 +0", "dd MMM yy HH:mm z", - "2016-01-12T15:04:00+00:00", 11) + "2016-01-12T15:04:00Z", 11) # RFC822Z = "02 Jan 06 15:04 -0700" # RFC822 with numeric zone parseTest("01 Mar 16 15:04 -07:00", "dd MMM yy HH:mm zzz", - "2016-03-01T22:04:00+00:00", 60) # day after february in leap year + "2016-03-01T22:04:00Z", 60) # day after february in leap year # RFC850 = "Monday, 02-Jan-06 15:04:05 MST" parseTest("Monday, 12-Jan-06 15:04:05 +0", "dddd, dd-MMM-yy HH:mm:ss z", - "2006-01-12T15:04:05+00:00", 11) + "2006-01-12T15:04:05Z", 11) # RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" parseTest("Sun, 01 Mar 2015 15:04:05 +0", "ddd, dd MMM yyyy HH:mm:ss z", - "2015-03-01T15:04:05+00:00", 59) # day after february in non-leap year + "2015-03-01T15:04:05Z", 59) # day after february in non-leap year # RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" # RFC1123 with numeric zone parseTest("Thu, 12 Jan 2006 15:04:05 -07:00", "ddd, dd MMM yyyy HH:mm:ss zzz", - "2006-01-12T22:04:05+00:00", 11) + "2006-01-12T22:04:05Z", 11) # RFC3339 = "2006-01-02T15:04:05Z07:00" - parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-ddTHH:mm:ssZzzz", - "2006-01-12T22:04:05+00:00", 11) parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-dd'T'HH:mm:ss'Z'zzz", - "2006-01-12T22:04:05+00:00", 11) + "2006-01-12T22:04:05Z", 11) # RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" parseTest("2006-01-12T15:04:05.999999999Z-07:00", - "yyyy-MM-ddTHH:mm:ss.999999999Zzzz", "2006-01-12T22:04:05+00:00", 11) + "yyyy-MM-dd'T'HH:mm:ss'.999999999Z'zzz", "2006-01-12T22:04:05Z", 11) for tzFormat in ["z", "zz", "zzz"]: # formatting timezone as 'Z' for UTC parseTest("2001-01-12T22:04:05Z", "yyyy-MM-dd'T'HH:mm:ss" & tzFormat, - "2001-01-12T22:04:05+00:00", 11) + "2001-01-12T22:04:05Z", 11) # Kitchen = "3:04PM" parseTestTimeOnly("3:04PM", "h:mmtt", "15:04:00") - #when not defined(testing): - # echo "Kitchen: " & $s.parse(f) - # var ti = timeToTimeInfo(getTime()) - # echo "Todays date after decoding: ", ti - # var tint = timeToTimeInterval(getTime()) - # echo "Todays date after decoding to interval: ", tint # Bug with parse not setting DST properly if the current local DST differs from # the date being parsed. Need to test parse dates both in and out of DST. We @@ -195,8 +188,8 @@ template runTimezoneTests() = let parsedJan = parse("2016-01-05 04:00:00+01:00", "yyyy-MM-dd HH:mm:sszzz") parsedJul = parse("2016-07-01 04:00:00+01:00", "yyyy-MM-dd HH:mm:sszzz") - doAssert toTime(parsedJan).toUnix == 1451962800 - doAssert toTime(parsedJul).toUnix == 1467342000 + check toTime(parsedJan).toUnix == 1451962800 + check toTime(parsedJul).toUnix == 1467342000 suite "ttimes": @@ -256,7 +249,7 @@ suite "ttimes": check $(dt + initDuration(days = 1)) == "2017-03-26T13:00:00+02:00" test "datetime before epoch": - check $fromUnix(-2147483648).utc == "1901-12-13T20:45:52+00:00" + check $fromUnix(-2147483648).utc == "1901-12-13T20:45:52Z" test "adding/subtracting time across dst": putenv("TZ", "Europe/Stockholm") @@ -319,6 +312,15 @@ suite "ttimes": test "incorrect inputs: timezone (zzz) 3": parseTestExcp("2018-02-19 16:30:00 +01:0", "yyyy-MM-dd hh:mm:ss zzz") + test "incorrect inputs: year (yyyy/uuuu)": + parseTestExcp("-0001", "yyyy") + parseTestExcp("-0001", "YYYY") + parseTestExcp("1", "yyyy") + parseTestExcp("12345", "yyyy") + parseTestExcp("1", "uuuu") + parseTestExcp("12345", "uuuu") + parseTestExcp("-1 BC", "UUUU g") + test "dynamic timezone": proc staticOffset(offset: int): Timezone = proc zoneInfoFromTz(adjTime: Time): ZonedTime = @@ -340,7 +342,7 @@ suite "ttimes": check dt.utcOffset == -9000 check dt.isDst == false check $dt == "2000-01-01T12:00:00+02:30" - check $dt.utc == "2000-01-01T09:30:00+00:00" + check $dt.utc == "2000-01-01T09:30:00Z" check $dt.utc.inZone(tz) == $dt test "isLeapYear": @@ -351,12 +353,12 @@ suite "ttimes": test "subtract months": var dt = initDateTime(1, mFeb, 2017, 00, 00, 00, utc()) - check $(dt - initTimeInterval(months = 1)) == "2017-01-01T00:00:00+00:00" + check $(dt - initTimeInterval(months = 1)) == "2017-01-01T00:00:00Z" dt = initDateTime(15, mMar, 2017, 00, 00, 00, utc()) - check $(dt - initTimeInterval(months = 1)) == "2017-02-15T00:00:00+00:00" + check $(dt - initTimeInterval(months = 1)) == "2017-02-15T00:00:00Z" dt = initDateTime(31, mMar, 2017, 00, 00, 00, utc()) # This happens due to monthday overflow. It's consistent with Phobos. - check $(dt - initTimeInterval(months = 1)) == "2017-03-03T00:00:00+00:00" + check $(dt - initTimeInterval(months = 1)) == "2017-03-03T00:00:00Z" test "duration": let d = initDuration @@ -384,11 +386,11 @@ suite "ttimes": discard initDateTime(1, mJan, -35_000, 12, 00, 00) discard initDateTime(1, mJan, 35_000, 12, 00, 00) # with duration/timeinterval - let dt = initDateTime(1, mJan, 35_000, 12, 00, 00, utc()) + + let dt = initDateTime(1, mJan, -35_000, 12, 00, 00, utc()) + initDuration(seconds = 1) check dt.second == 1 let dt2 = dt + 35_001.years - check $dt2 == "0001-01-01T12:00:01+00:00" + check $dt2 == "0001-01-01T12:00:01Z" test "compare datetimes": var dt1 = now() @@ -426,4 +428,90 @@ suite "ttimes": check (-1).fromWinTime.nanosecond == convert(Seconds, Nanoseconds, 1) - 100 check -1.fromWinTime.toWinTime == -1 # One nanosecond is discarded due to differences in time resolution - check initTime(0, 101).toWinTime.fromWinTime.nanosecond == 100 \ No newline at end of file + check initTime(0, 101).toWinTime.fromWinTime.nanosecond == 100 + check initTime(0, 101).toWinTime.fromWinTime.nanosecond == 100 + + test "issue 7620": + let layout = "M/d/yyyy' 'h:mm:ss' 'tt' 'z" + let t7620_am = parse("4/15/2017 12:01:02 AM +0", layout, utc()) + check t7620_am.format(layout) == "4/15/2017 12:01:02 AM Z" + let t7620_pm = parse("4/15/2017 12:01:02 PM +0", layout, utc()) + check t7620_pm.format(layout) == "4/15/2017 12:01:02 PM Z" + + test "format": + var dt = initDateTime(1, mJan, -0001, + 17, 01, 02, 123_456_789, + staticTz(hours = 1, minutes = 2, seconds = 3)) + check dt.format("d") == "1" + check dt.format("dd") == "01" + check dt.format("ddd") == "Fri" + check dt.format("dddd") == "Friday" + check dt.format("h") == "5" + check dt.format("hh") == "05" + check dt.format("H") == "17" + check dt.format("HH") == "17" + check dt.format("m") == "1" + check dt.format("mm") == "01" + check dt.format("M") == "1" + check dt.format("MM") == "01" + check dt.format("MMM") == "Jan" + check dt.format("MMMM") == "January" + check dt.format("s") == "2" + check dt.format("ss") == "02" + check dt.format("t") == "P" + check dt.format("tt") == "PM" + check dt.format("yy") == "02" + check dt.format("yyyy") == "0002" + check dt.format("YYYY") == "2" + check dt.format("uuuu") == "-0001" + check dt.format("UUUU") == "-1" + check dt.format("z") == "-1" + check dt.format("zz") == "-01" + check dt.format("zzz") == "-01:02" + check dt.format("zzzz") == "-01:02:03" + check dt.format("g") == "BC" + + check dt.format("fff") == "123" + check dt.format("ffffff") == "123456" + check dt.format("fffffffff") == "123456789" + dt.nanosecond = 1 + check dt.format("fff") == "000" + check dt.format("ffffff") == "000000" + check dt.format("fffffffff") == "000000001" + + dt.year = 12345 + check dt.format("yyyy") == "+12345" + check dt.format("uuuu") == "+12345" + dt.year = -12345 + check dt.format("yyyy") == "+12346" + check dt.format("uuuu") == "-12345" + + expect ValueError: + discard initTimeFormat("'") + + expect ValueError: + discard initTimeFormat("'foo") + + expect ValueError: + discard initTimeFormat("foo'") + + test "parse": + check $parse("20180101", "yyyyMMdd", utc()) == "2018-01-01T00:00:00Z" + parseTestExcp("+120180101", "yyyyMMdd") + + check parse("1", "YYYY", utc()).year == 1 + check parse("1 BC", "YYYY g", utc()).year == 0 + check parse("0001 BC", "yyyy g", utc()).year == 0 + check parse("+12345 BC", "yyyy g", utc()).year == -12344 + check parse("1 AD", "YYYY g", utc()).year == 1 + check parse("0001 AD", "yyyy g", utc()).year == 1 + check parse("+12345 AD", "yyyy g", utc()).year == 12345 + + check parse("-1", "UUUU", utc()).year == -1 + check parse("-0001", "uuuu", utc()).year == -1 + + discard parse("foobar", "'foobar'") + discard parse("foo'bar", "'foo''''bar'") + discard parse("'", "''") + + parseTestExcp("2000 A", "yyyy g") diff --git a/tests/system/toString.nim b/tests/system/tostring.nim index ea10f998c..9a6b83f95 100644 --- a/tests/system/toString.nim +++ b/tests/system/tostring.nim @@ -1,12 +1,12 @@ discard """ - output:"" + output: "" """ doAssert "@[23, 45]" == $(@[23, 45]) doAssert "[32, 45]" == $([32, 45]) doAssert """@["", "foo", "bar"]""" == $(@["", "foo", "bar"]) -doAssert """["", "foo", "bar"]""" == $(["", "foo", "bar"]) -doAssert """["", "foo", "bar"]""" == $(@["", "foo", "bar"].toOpenArray(0, 2)) +doAssert """["", "foo", "bar"]""" == $(["", "foo", "bar"]) +doAssert """["", "foo", "bar"]""" == $(@["", "foo", "bar"].toOpenArray(0, 2)) # bug #2395 let alphaSet: set[char] = {'a'..'c'} @@ -69,13 +69,13 @@ var yy: string doAssert xx == @[] doAssert yy == "" -proc bar(arg: cstring): void = +proc bar(arg: cstring) = doAssert arg[0] == '\0' -proc baz(arg: openarray[char]): void = +proc baz(arg: openarray[char]) = doAssert arg.len == 0 -proc stringCompare(): void = +proc stringCompare() = var a,b,c,d,e,f,g: string a.add 'a' doAssert a == "a" @@ -102,9 +102,12 @@ proc stringCompare(): void = doAssert "" != "\0\0\0\0\0\0\0\0\0\0" var nilstring: string - bar(nilstring) + #bar(nilstring) baz(nilstring) stringCompare() +var nilstring: string +bar(nilstring) + static: stringCompare() \ No newline at end of file diff --git a/tests/untestable/gdb/gdb_pretty_printer_test.py b/tests/untestable/gdb/gdb_pretty_printer_test.py new file mode 100644 index 000000000..54af65d9a --- /dev/null +++ b/tests/untestable/gdb/gdb_pretty_printer_test.py @@ -0,0 +1,33 @@ +import gdb +# this test should test the gdb pretty printers of the nim +# library. But be aware this test is not complete. It only tests the +# command line version of gdb. It does not test anything for the +# machine interface of gdb. This means if if this test passes gdb +# frontends might still be broken. + +gdb.execute("source ../../../tools/nim-gdb.py") +# debug all instances of the generic function `myDebug`, should be 8 +gdb.execute("rbreak myDebug") +gdb.execute("run") + +outputs = [ + 'meTwo', + '"meTwo"', + '{meOne, meThree}', + 'MyOtherEnum(1)', + '5', + 'array = {1, 2, 3, 4, 5}', + 'seq(3, 3) = {"one", "two", "three"}', + 'Table(3, 64) = {["two"] = 2, ["three"] = 3, ["one"] = 1}', +] + +for i, expected in enumerate(outputs): + if i == 5: + # myArray is passed as pointer to int to myDebug. I look up myArray up in the stack + gdb.execute("up") + output = str(gdb.parse_and_eval("myArray")) + else: + output = str(gdb.parse_and_eval("arg")) + + assert output == expected, output + " != " + expected + gdb.execute("continue") diff --git a/tests/untestable/gdb/gdb_pretty_printer_test_output.txt b/tests/untestable/gdb/gdb_pretty_printer_test_output.txt new file mode 100644 index 000000000..cbc9bde8d --- /dev/null +++ b/tests/untestable/gdb/gdb_pretty_printer_test_output.txt @@ -0,0 +1,3 @@ +Loading Nim Runtime support. +NimEnumPrinter: lookup global symbol 'NTI_z9cu80OJCfNgw9bUdzn5ZEzw_ failed for tyEnum_MyOtherEnum_z9cu80OJCfNgw9bUdzn5ZEzw. +8 diff --git a/tests/untestable/gdb/gdb_pretty_printer_test_program.nim b/tests/untestable/gdb/gdb_pretty_printer_test_program.nim new file mode 100644 index 000000000..458435c1a --- /dev/null +++ b/tests/untestable/gdb/gdb_pretty_printer_test_program.nim @@ -0,0 +1,53 @@ + + +import tables + +type + MyEnum = enum + meOne, + meTwo, + meThree, + meFour, + + MyOtherEnum = enum + moOne, + moTwo, + moThree, + moFoure, + + +var counter = 0 + +proc myDebug[T](arg: T): void = + counter += 1 + +proc testProc(): void = + var myEnum = meTwo + myDebug(myEnum) + # create a string object but also make the NTI for MyEnum is generated + var myString = $myEnum + myDebug(myString) + var mySet = {meOne,meThree} + myDebug(mySet) + + # for MyOtherEnum there is no NTI. This tests the fallback for the pretty printer. + var moEnum = moTwo + myDebug(moEnum) + var moSet = {moOne,moThree} + myDebug(moSet) + + let myArray = [1,2,3,4,5] + myDebug(myArray) + let mySeq = @["one","two","three"] + myDebug(mySeq) + + var myTable = initTable[string, int]() + myTable["one"] = 1 + myTable["two"] = 2 + myTable["three"] = 3 + myDebug(myTable) + + echo(counter) + + +testProc() diff --git a/tests/untestable/gdb/gdb_pretty_printer_test_run.sh b/tests/untestable/gdb/gdb_pretty_printer_test_run.sh new file mode 100755 index 000000000..525f54705 --- /dev/null +++ b/tests/untestable/gdb/gdb_pretty_printer_test_run.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Exit if anything fails +set -e +#!/usr/bin/env bash +# Compile the test project with fresh debug information. +nim c --debugger:native gdb_pretty_printer_test_program.nim &> /dev/null +# 2>&1 redirects stderr to stdout (all output in stdout) +# <(...) is a bash feature that makes the output of a command into a +# file handle. +# diff compares the two files, the expected output, and the file +# handle that is created by the execution of gdb. +diff ./gdb_pretty_printer_test_output.txt <(gdb -x gdb_pretty_printer_test.py --batch-silent --args gdb_pretty_printer_test_program 2>&1) +# The exit code of diff is forwarded as the exit code of this +# script. So when the comparison fails, the exit code of this script +# won't be 0. So this script should be embeddable in a test suite. diff --git a/tools/nim-gdb.py b/tools/nim-gdb.py new file mode 100644 index 000000000..b98dc96fe --- /dev/null +++ b/tools/nim-gdb.py @@ -0,0 +1,513 @@ + +import gdb +import re +import sys + +# some feedback that the nim runtime support is loading, isn't a bad +# thing at all. +gdb.write("Loading Nim Runtime support.\n", gdb.STDERR) + +# When error occure they occur regularly. This 'caches' known errors +# and prevents them from being reprinted over and over again. +errorSet = set() +def printErrorOnce(id, message): + global errorSet + if id not in errorSet: + errorSet.add(id) + gdb.write(message, gdb.STDERR) + +nimobjfile = gdb.current_objfile() or gdb.objfiles()[0] +nimobjfile.type_printers = [] + +################################################################################ +##### Type pretty printers +################################################################################ + +type_hash_regex = re.compile("^\w*_([A-Za-z0-9]*)$") + +def getNimRti(type_name): + """ Return a ``gdb.Value`` object for the Nim Runtime Information of ``type_name``. """ + + # Get static const TNimType variable. This should be available for + # every non trivial Nim type. + m = type_hash_regex.match(type_name) + if m: + try: + return gdb.parse_and_eval("NTI_" + m.group(1) + "_") + except: + return None + +class NimTypeRecognizer: + # this type map maps from types that are generated in the C files to + # how they are called in nim. To not mix up the name ``int`` from + # system.nim with the name ``int`` that could still appear in + # generated code, ``NI`` is mapped to ``system.int`` and not just + # ``int``. + + type_map_static = { + 'NI': 'system.int', 'NI8': 'int8', 'NI16': 'int16', 'NI32': 'int32', 'NI64': 'int64', + 'NU': 'uint', 'NU8': 'uint8','NU16': 'uint16', 'NU32': 'uint32', 'NU64': 'uint64', + 'NF': 'float', 'NF32': 'float32', 'NF64': 'float64', + 'NIM_BOOL': 'bool', 'NIM_CHAR': 'char', 'NCSTRING': 'cstring', + 'NimStringDesc': 'string' + } + + # Normally gdb distinguishes between the command `ptype` and + # `whatis`. `ptype` prints a very detailed view of the type, and + # `whatis` a very brief representation of the type. I haven't + # figured out a way to know from the type printer that is + # implemented here how to know if a type printer should print the + # short representation or the long representation. As a hacky + # workaround I just say I am not resposible for printing pointer + # types (seq and string are exception as they are semantically + # values). this way the default type printer will handle pointer + # types and dive into the members of that type. So I can still + # control with `ptype myval` and `ptype *myval` if I want to have + # detail or not. I this this method stinks but I could not figure + # out a better solution. + + object_type_pattern = re.compile("^(\w*):ObjectType$") + + def recognize(self, type_obj): + + tname = None + if type_obj.tag is not None: + tname = type_obj.tag + elif type_obj.name is not None: + tname = type_obj.name + + # handle pointer types + if not tname: + if type_obj.code == gdb.TYPE_CODE_PTR: + target_type = type_obj.target() + target_type_name = target_type.name + if target_type_name: + # visualize 'string' as non pointer type (unpack pointer type). + if target_type_name == "NimStringDesc": + tname = target_type_name # could also just return 'string' + # visualize 'seq[T]' as non pointer type. + if target_type_name.find('tySequence_') == 0: + tname = target_type_name + + if not tname: + # We are not resposible for this type printing. + # Basically this means we don't print pointer types. + return None + + result = self.type_map_static.get(tname, None) + if result: + return result + + rti = getNimRti(tname) + if rti: + return rti['name'].string("utf-8", "ignore") + else: + return None + +class NimTypePrinter: + """Nim type printer. One printer for all Nim types.""" + + + # enabling and disabling of type printers can be done with the + # following gdb commands: + # + # enable type-printer NimTypePrinter + # disable type-printer NimTypePrinter + + name = "NimTypePrinter" + def __init__ (self): + self.enabled = True + + def instantiate(self): + return NimTypeRecognizer() + + +nimobjfile.type_printers = [NimTypePrinter()] + +################################################################################ +##### GDB Function, equivalent of Nim's $ operator +################################################################################ + +class DollarPrintFunction (gdb.Function): + "Nim's equivalent of $ operator as a gdb function, available in expressions `print $dollar(myvalue)" + + _gdb_dollar_functions = gdb.execute("info functions dollar__", True, True) + dollar_functions = re.findall('NimStringDesc \*(dollar__[A-z0-9_]+?)\(([^,)]*)\);', _gdb_dollar_functions) + + def __init__ (self): + super (DollarPrintFunction, self).__init__("dollar") + + @staticmethod + def invoke_static(arg): + + for func, arg_typ in DollarPrintFunction.dollar_functions: + + if arg.type.name == arg_typ: + func_value = gdb.lookup_global_symbol(func, gdb.SYMBOL_FUNCTIONS_DOMAIN).value() + return func_value(arg) + + if arg.type.name + " *" == arg_typ: + func_value = gdb.lookup_global_symbol(func, gdb.SYMBOL_FUNCTIONS_DOMAIN).value() + return func_value(arg.address) + + typeName = arg.type.name + printErrorOnce(typeName, "No suitable Nim $ operator found for type: " + typeName + ".\n") + + def invoke(self, arg): + return self.invoke_static(arg) + +DollarPrintFunction() + +################################################################################ +##### GDB Command, equivalent of Nim's $ operator +################################################################################ + +class DollarPrintCmd (gdb.Command): + """Dollar print command for Nim, `$ expr` will invoke Nim's $ operator""" + + def __init__ (self): + super (DollarPrintCmd, self).__init__ ("$", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION) + + def invoke (self, arg, from_tty): + param = gdb.parse_and_eval(arg) + gdb.write(str(DollarPrintFunction.invoke_static(param)) + "\n", gdb.STDOUT) + +DollarPrintCmd() + +################################################################################ +##### Value pretty printers +################################################################################ + +class NimBoolPrinter: + + pattern = re.compile(r'^NIM_BOOL$') + + def __init__(self, val): + self.val = val + + def to_string(self): + if self.val == 0: + return "false" + else: + return "true" + +################################################################################ + +class NimStringPrinter: + pattern = re.compile(r'^NimStringDesc \*$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'string' + + def to_string(self): + if self.val: + l = int(self.val['Sup']['len']) + return self.val['data'][0].address.string("utf-8", "ignore", l) + else: + return "" + +################################################################################ + +# proc reprEnum(e: int, typ: PNimType): string {.compilerRtl.} = +# ## Return string representation for enumeration values +# var n = typ.node +# if ntfEnumHole notin typ.flags: +# let o = e - n.sons[0].offset +# if o >= 0 and o <% typ.node.len: +# return $n.sons[o].name +# else: +# # ugh we need a slow linear search: +# var s = n.sons +# for i in 0 .. n.len-1: +# if s[i].offset == e: +# return $s[i].name +# result = $e & " (invalid data!)" + +def reprEnum(e, typ): + """ this is a port of the nim runtime function `reprEnum` to python """ + e = int(e) + n = typ["node"] + flags = int(typ["flags"]) + # 1 << 2 is {ntfEnumHole} + if ((1 << 2) & flags) == 0: + o = e - int(n["sons"][0]["offset"]) + if o >= 0 and 0 < int(n["len"]): + return n["sons"][o]["name"].string("utf-8", "ignore") + else: + # ugh we need a slow linear search: + s = n["sons"] + for i in range(0, int(n["len"])): + if int(s[i]["offset"]) == e: + return s[i]["name"].string("utf-8", "ignore") + + return str(e) + " (invalid data!)" + +class NimEnumPrinter: + pattern = re.compile(r'^tyEnum_(\w*)_([A-Za-z0-9]*)$') + + def __init__(self, val): + self.val = val + match = self.pattern.match(self.val.type.name) + self.typeNimName = match.group(1) + typeInfoName = "NTI_" + match.group(2) + "_" + self.nti = gdb.lookup_global_symbol(typeInfoName) + + if self.nti is None: + printErrorOnce(typeInfoName, "NimEnumPrinter: lookup global symbol '" + typeInfoName + " failed for " + self.val.type.name + ".\n") + + def to_string(self): + if self.nti: + arg0 = self.val + arg1 = self.nti.value(gdb.newest_frame()) + return reprEnum(arg0, arg1) + else: + return self.typeNimName + "(" + str(int(self.val)) + ")" + +################################################################################ + +class NimSetPrinter: + ## the set printer is limited to sets that fit in an integer. Other + ## sets are compiled to `NU8 *` (ptr uint8) and are invisible to + ## gdb (currently). + pattern = re.compile(r'^tySet_tyEnum_(\w*)_([A-Za-z0-9]*)$') + + def __init__(self, val): + self.val = val + match = self.pattern.match(self.val.type.name) + self.typeNimName = match.group(1) + + typeInfoName = "NTI_" + match.group(2) + "_" + self.nti = gdb.lookup_global_symbol(typeInfoName) + + if self.nti is None: + printErrorOnce(typeInfoName, "NimSetPrinter: lookup global symbol '"+ typeInfoName +" failed for " + self.val.type.name + ".\n") + + def to_string(self): + if self.nti: + nti = self.nti.value(gdb.newest_frame()) + enumStrings = [] + val = int(self.val) + i = 0 + while val > 0: + if (val & 1) == 1: + enumStrings.append(reprEnum(i, nti)) + val = val >> 1 + i += 1 + + return '{' + ', '.join(enumStrings) + '}' + else: + return str(int(self.val)) + +################################################################################ + +class NimHashSetPrinter: + pattern = re.compile(r'^tyObject_(HashSet)_([A-Za-z0-9]*)$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'array' + + def to_string(self): + counter = 0 + capacity = 0 + if self.val: + counter = int(self.val['counter']) + if self.val['data']: + capacity = int(self.val['data']['Sup']['len']) + + return 'HashSet({0}, {1})'.format(counter, capacity) + + def children(self): + if self.val: + data = NimSeqPrinter(self.val['data']) + for idxStr, entry in data.children(): + if int(entry['Field0']) > 0: + yield ("data." + idxStr + ".Field1", str(entry['Field1'])) + +################################################################################ + +class NimSeqPrinter: + # the pointer is explicity part of the type. So it is part of + # ``pattern``. + pattern = re.compile(r'^tySequence_\w* \*$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'array' + + def to_string(self): + len = 0 + cap = 0 + if self.val: + len = int(self.val['Sup']['len']) + cap = int(self.val['Sup']['reserved']) + + return 'seq({0}, {1})'.format(len, cap) + + def children(self): + if self.val: + length = int(self.val['Sup']['len']) + #align = len(str(length - 1)) + for i in range(length): + yield ("data[{0}]".format(i), self.val["data"][i]) + +################################################################################ + +class NimArrayPrinter: + pattern = re.compile(r'^tyArray_\w*$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'array' + + def to_string(self): + return 'array' + + def children(self): + length = self.val.type.sizeof // self.val[0].type.sizeof + align = len(str(length-1)) + for i in range(length): + yield ("[{0:>{1}}]".format(i, align), self.val[i]) + +################################################################################ + +class NimStringTablePrinter: + pattern = re.compile(r'^tyObject_(StringTableObj)_([A-Za-z0-9]*)(:? \*)?$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'map' + + def to_string(self): + counter = 0 + capacity = 0 + if self.val: + counter = int(self.val['counter']) + if self.val['data']: + capacity = int(self.val['data']['Sup']['len']) + + return 'StringTableObj({0}, {1})'.format(counter, capacity) + + def children(self): + if self.val: + data = NimSeqPrinter(self.val['data']) + for idxStr, entry in data.children(): + if int(entry['Field2']) > 0: + yield (idxStr + ".Field0", entry['Field0']) + yield (idxStr + ".Field1", entry['Field1']) + +################################################################ + +class NimTablePrinter: + pattern = re.compile(r'^tyObject_(Table)_([A-Za-z0-9]*)(:? \*)?$') + + def __init__(self, val): + self.val = val + # match = self.pattern.match(self.val.type.name) + + def display_hint(self): + return 'map' + + def to_string(self): + counter = 0 + capacity = 0 + if self.val: + counter = int(self.val['counter']) + if self.val['data']: + capacity = int(self.val['data']['Sup']['len']) + + return 'Table({0}, {1})'.format(counter, capacity) + + def children(self): + if self.val: + data = NimSeqPrinter(self.val['data']) + for idxStr, entry in data.children(): + if int(entry['Field0']) > 0: + yield (idxStr + '.Field1', entry['Field1']) + yield (idxStr + '.Field2', entry['Field2']) + + +################################################################ + +# this is untested, therefore disabled + +# class NimObjectPrinter: +# pattern = re.compile(r'^tyObject_.*$') + +# def __init__(self, val): +# self.val = val + +# def display_hint(self): +# return 'object' + +# def to_string(self): +# return str(self.val.type) + +# def children(self): +# if not self.val: +# yield "object", "<nil>" +# raise StopIteration + +# for (i, field) in enumerate(self.val.type.fields()): +# if field.type.code == gdb.TYPE_CODE_UNION: +# yield _union_field +# else: +# yield (field.name, self.val[field]) + +# def _union_field(self, i, field): +# rti = getNimRti(self.val.type.name) +# if rti is None: +# return (field.name, "UNION field can't be displayed without RTI") + +# node_sons = rti['node'].dereference()['sons'] +# prev_field = self.val.type.fields()[i - 1] + +# descriminant_node = None +# for i in range(int(node['len'])): +# son = node_sons[i].dereference() +# if son['name'].string("utf-8", "ignore") == str(prev_field.name): +# descriminant_node = son +# break +# if descriminant_node is None: +# raise ValueError("Can't find union descriminant field in object RTI") + +# if descriminant_node is None: raise ValueError("Can't find union field in object RTI") +# union_node = descriminant_node['sons'][int(self.val[prev_field])].dereference() +# union_val = self.val[field] + +# for f1 in union_val.type.fields(): +# for f2 in union_val[f1].type.fields(): +# if str(f2.name) == union_node['name'].string("utf-8", "ignore"): +# return (str(f2.name), union_val[f1][f2]) + +# raise ValueError("RTI is absent or incomplete, can't find union definition in RTI") + + +################################################################################ + +def makematcher(klass): + def matcher(val): + typeName = str(val.type) + try: + if hasattr(klass, 'pattern') and hasattr(klass, '__name__'): + # print(typeName + " <> " + klass.__name__) + if klass.pattern.match(typeName): + return klass(val) + except Exception as e: + print(klass) + printErrorOnce(typeName, "No matcher for type '" + typeName + "': " + str(e) + "\n") + return matcher + +nimobjfile.pretty_printers = [] +nimobjfile.pretty_printers.extend([makematcher(var) for var in list(vars().values()) if hasattr(var, 'pattern')]) |