diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 76c94c8e75..c37b4c8d4b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,7 +8,7 @@ stages: .linux_set_path: &linux_set_path_def before_script: - - export PATH=$(pwd)/bin:$PATH + - export PATH=$(pwd)/bin${PATH:+:$PATH} tags: - linux diff --git a/.travis.yml b/.travis.yml index 095c3ec74f..a3fa3f1da1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ before_script: - sh build.sh - cd .. - sed -i -e 's,cc = gcc,cc = clang,' config/nim.cfg - - export PATH=$(pwd)/bin:$PATH + - export PATH=$(pwd)/bin${PATH:+:$PATH} script: - nim c koch - ./koch boot @@ -48,3 +48,5 @@ script: - ./koch csource - ./koch nimsuggest # - nim c -r nimsuggest/tester + - ( ! grep -F '.. code-block' -l -r --include '*.html' --exclude contributing.html --exclude docgen.html --exclude tut2.html ) + - ( ! grep -F '..code-block' -l -r --include '*.html' --exclude contributing.html --exclude docgen.html --exclude tut2.html ) diff --git a/changelog.md b/changelog.md index 5858ec28f8..4dc2310c32 100644 --- a/changelog.md +++ b/changelog.md @@ -53,36 +53,37 @@ what to return if the environment variable does not exist. - Bodies of ``for`` loops now get their own scope: -.. code-block:: nim +```nim # now compiles: for i in 0..4: let i = i + 1 echo i +``` - The parsing rules of ``if`` expressions were changed so that multiple statements are allowed in the branches. We found few code examples that now fail because of this change, but here is one: -.. code-block:: nim - +```nim t[ti] = if exp_negative: '-' else: '+'; inc(ti) +``` This now needs to be written as: -.. code-block:: nim - +```nim t[ti] = (if exp_negative: '-' else: '+'); inc(ti) +``` - To make Nim even more robust the system iterators ``..`` and ``countup`` now only accept a single generic type ``T``. This means the following code doesn't die with an "out of range" error anymore: -.. code-block:: nim - +```nim var b = 5.Natural var a = -5 for i in a..b: echo i +``` - ``formatFloat``/``formatBiggestFloat`` now support formatting floats with zero precision digits. The previous ``precision = 0`` behavior (default formatting) @@ -115,13 +116,17 @@ This now needs to be written as: - Nim's ``rst2html`` command now supports the testing of code snippets via an RST extension that we called ``:test:``:: + ```rst .. code-block:: nim :test: # shows how the 'if' statement works if true: echo "yes" + ``` - The ``[]`` proc for strings now raises an ``IndexError`` exception when the specified slice is out of bounds. See issue [#6223](https://github.com/nim-lang/Nim/issues/6223) for more details. + You can use ``substr(str, start, finish)`` to get the old behaviour back, + see [this commit](https://github.com/nim-lang/nimbot/commit/98cc031a27ea89947daa7f0bb536bcf86462941f) for an example. - ``strutils.split`` and ``strutils.rsplit`` with an empty string and a separator now returns that empty string. See issue [#4377](https://github.com/nim-lang/Nim/issues/4377). @@ -137,3 +142,68 @@ This now needs to be written as: to [http://www.gii.upv.es/tlsf/](http://www.gii.upv.es/tlsf/) the maximum fragmentation measured is lower than 25%. As a nice bonus ``alloc`` and ``dealloc`` became O(1) operations. +- The behavior of ``$`` has been changed for all standard library collections. The + collection-to-string implementations now perform proper quoting and escaping of + strings and chars. +- The ``random`` procs in ``random.nim`` have all been deprecated. Instead use + the new ``rand`` procs. The module now exports the state of the random + number generator as type ``Rand`` so multiple threads can easily use their + own random number generators that do not require locking. For more information + about this rename see issue [#6934](https://github.com/nim-lang/Nim/issues/6934) +- The compiler is now more consistent in its treatment of ambiguous symbols: + Types that shadow procs and vice versa are marked as ambiguous (bug #6693). +- ``yield`` (or ``await`` which is mapped to ``yield``) never worked reliably + in an array, seq or object constructor and is now prevented at compile-time. +- For string formatting / interpolation a new module + called [strformat](https://nim-lang.org/docs/strformat.html) has been added + to the stdlib. +- codegenDecl pragma now works for the JavaScript backend. It returns an empty string for + function return type placeholders. +- Asynchronous programming for the JavaScript backend using the `asyncjs` module. +- Extra semantic checks for procs with noreturn pragma: return type is not allowed, + statements after call to noreturn procs are no longer allowed. +- Noreturn proc calls and raising exceptions branches are now skipped during common type + deduction in if and case expressions. The following code snippets now compile: +```nim +import strutils +let str = "Y" +let a = case str: + of "Y": true + of "N": false + else: raise newException(ValueError, "Invalid boolean") +let b = case str: + of nil, "": raise newException(ValueError, "Invalid boolean") + elif str.startsWith("Y"): true + elif str.startsWith("N"): false + else: false +let c = if str == "Y": true + elif str == "N": false + else: + echo "invalid bool" + quit("this is the end") +``` +- Proc [toCountTable](https://nim-lang.org/docs/tables.html#toCountTable,openArray[A]) now produces a `CountTable` with values correspoding to the number of occurrences of the key in the input. It used to produce a table with all values set to `1`. + +Counting occurrences in a sequence used to be: + +```nim +let mySeq = @[1, 2, 1, 3, 1, 4] +var myCounter = initCountTable[int]() + +for item in mySeq: + myCounter.inc item +``` + +Now, you can simply do: + +```nim +let + mySeq = @[1, 2, 1, 3, 1, 4] + myCounter = mySeq.toCountTable() +``` + +- Added support for casting between integers of same bitsize in VM (compile time and nimscript). + This allow to among other things to reinterpret signed integers as unsigned. +- Pragmas now support call syntax, for example: ``{.exportc"myname".}`` and ``{.exportc("myname").}`` +- Custom pragmas are now supported using pragma ``pragma``, please see language manual for details + diff --git a/ci/build.sh b/ci/build.sh index a0fee14977..6321fffba8 100644 --- a/ci/build.sh +++ b/ci/build.sh @@ -6,7 +6,7 @@ cd csources sh build.sh cd .. # Add Nim to the PATH -export PATH=$(pwd)/bin:$PATH +export PATH=$(pwd)/bin${PATH:+:$PATH} # Bootstrap. nim -v nim c koch diff --git a/ci/deps.sh b/ci/deps.sh index 7471785a0d..f0f831a2a3 100644 --- a/ci/deps.sh +++ b/ci/deps.sh @@ -7,7 +7,7 @@ apt-get install -y -qq build-essential git libcurl4-openssl-dev libsdl1.2-dev li gcc -v -export PATH=$(pwd)/bin:$PATH +export PATH=$(pwd)/bin${PATH:+:$PATH} # Nimble deps nim e install_nimble.nims diff --git a/compiler/aliases.nim b/compiler/aliases.nim index c0371e1596..490ac987a7 100644 --- a/compiler/aliases.nim +++ b/compiler/aliases.nim @@ -49,7 +49,7 @@ proc isPartOfAux(a, b: PType, marker: var IntSet): TAnalysisResult = if a.sons[0] != nil: result = isPartOfAux(a.sons[0].skipTypes(skipPtrs), b, marker) if result == arNo: result = isPartOfAux(a.n, b, marker) - of tyGenericInst, tyDistinct, tyAlias: + of tyGenericInst, tyDistinct, tyAlias, tySink: result = isPartOfAux(lastSon(a), b, marker) of tyArray, tySet, tyTuple: for i in countup(0, sonsLen(a) - 1): @@ -179,5 +179,11 @@ proc isPartOf*(a, b: PNode): TAnalysisResult = result = isPartOf(a[0], b) if result == arNo: result = arMaybe else: discard + of nkObjConstr: + result = arNo + for i in 1..data[$2]", rdLoc(d), intLiteral(i)) + arr.r = rfmt(nil, "$1->data[$2]", rdLoc(dest[]), intLiteral(i)) arr.storage = OnHeap # we know that sequences are on the heap expr(p, n[i], arr) gcUsage(n) + if doesAlias: + if d.k == locNone: + d = tmp + else: + genAssignment(p, d, tmp, {}) proc genArrToSeq(p: BProc, n: PNode, d: var TLoc) = var elem, a, arr: TLoc @@ -1692,7 +1723,7 @@ proc genRangeChck(p: BProc, n: PNode, d: var TLoc, magic: string) = rope(magic)]), a.storage) proc genConv(p: BProc, e: PNode, d: var TLoc) = - let destType = e.typ.skipTypes({tyVar, tyGenericInst, tyAlias}) + let destType = e.typ.skipTypes({tyVar, tyGenericInst, tyAlias, tySink}) if sameBackendType(destType, e.sons[1].typ): expr(p, e.sons[1], d) else: @@ -1768,7 +1799,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = "$# = #subInt64($#, $#);$n"] const fun: array[mInc..mDec, string] = ["$# = #addInt($#, $#);$n", "$# = #subInt($#, $#);$n"] - let underlying = skipTypes(e.sons[1].typ, {tyGenericInst, tyAlias, tyVar, tyRange}) + let underlying = skipTypes(e.sons[1].typ, {tyGenericInst, tyAlias, tySink, tyVar, tyRange}) if optOverflowCheck notin p.options or underlying.kind in {tyUInt..tyUInt64}: binaryStmt(p, e, d, opr[op]) else: @@ -1778,7 +1809,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) - let ranged = skipTypes(e.sons[1].typ, {tyGenericInst, tyAlias, tyVar}) + let ranged = skipTypes(e.sons[1].typ, {tyGenericInst, tyAlias, tySink, tyVar, tyLent}) let res = binaryArithOverflowRaw(p, ranged, a, b, if underlying.kind == tyInt64: fun64[op] else: fun[op]) putIntoDest(p, a, e.sons[1], "($#)($#)" % [ @@ -1968,10 +1999,35 @@ proc genComplexConst(p: BProc, sym: PSym, d: var TLoc) = assert((sym.loc.r != nil) and (sym.loc.t != nil)) putLocIntoDest(p, d, sym.loc) +template genStmtListExprImpl(exprOrStmt) {.dirty.} = + #let hasNimFrame = magicsys.getCompilerProc("nimFrame") != nil + let hasNimFrame = p.prc != nil and + sfSystemModule notin p.module.module.flags and + optStackTrace in p.prc.options + var frameName: Rope = nil + for i in 0 .. n.len - 2: + let it = n[i] + if it.kind == nkComesFrom: + if hasNimFrame and frameName == nil: + inc p.labels + frameName = "FR" & rope(p.labels) & "_" + let theMacro = it[0].sym + add p.s(cpsStmts), initFrameNoDebug(p, frameName, + makeCString theMacro.name.s, + theMacro.info.quotedFilename, it.info.line) + else: + genStmts(p, it) + if n.len > 0: exprOrStmt + if frameName != nil: + add p.s(cpsStmts), deinitFrameNoDebug(p, frameName) + proc genStmtListExpr(p: BProc, n: PNode, d: var TLoc) = - var length = sonsLen(n) - for i in countup(0, length - 2): genStmts(p, n.sons[i]) - if length > 0: expr(p, n.sons[length - 1], d) + genStmtListExprImpl: + expr(p, n[n.len - 1], d) + +proc genStmtList(p: BProc, n: PNode) = + genStmtListExprImpl: + genStmts(p, n[n.len - 1]) proc upConv(p: BProc, n: PNode, d: var TLoc) = var a: TLoc @@ -1981,9 +2037,9 @@ proc upConv(p: BProc, n: PNode, d: var TLoc) = var r = rdLoc(a) var nilCheck: Rope = nil var t = skipTypes(a.t, abstractInst) - while t.kind in {tyVar, tyPtr, tyRef}: - if t.kind != tyVar: nilCheck = r - if t.kind != tyVar or not p.module.compileToCpp: + while t.kind in {tyVar, tyLent, tyPtr, tyRef}: + if t.kind notin {tyVar, tyLent}: nilCheck = r + if t.kind notin {tyVar, tyLent} or not p.module.compileToCpp: r = "(*$1)" % [r] t = skipTypes(t.lastSon, abstractInst) if not p.module.compileToCpp: @@ -2016,7 +2072,7 @@ proc downConv(p: BProc, n: PNode, d: var TLoc) = var a: TLoc initLocExpr(p, arg, a) var r = rdLoc(a) - let isRef = skipTypes(arg.typ, abstractInst).kind in {tyRef, tyPtr, tyVar} + let isRef = skipTypes(arg.typ, abstractInst).kind in {tyRef, tyPtr, tyVar, tyLent} if isRef: add(r, "->Sup") else: @@ -2028,7 +2084,7 @@ proc downConv(p: BProc, n: PNode, d: var TLoc) = # (see bug #837). However sometimes using a temporary is not correct: # init(TFigure(my)) # where it is passed to a 'var TFigure'. We test # this by ensuring the destination is also a pointer: - if d.k == locNone and skipTypes(n.typ, abstractInst).kind in {tyRef, tyPtr, tyVar}: + if d.k == locNone and skipTypes(n.typ, abstractInst).kind in {tyRef, tyPtr, tyVar, tyLent}: getTemp(p, n.typ, d) linefmt(p, cpsStmts, "$1 = &$2;$n", rdLoc(d), r) else: @@ -2170,8 +2226,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = of nkCheckedFieldExpr: genCheckedRecordField(p, n, d) of nkBlockExpr, nkBlockStmt: genBlock(p, n, d) of nkStmtListExpr: genStmtListExpr(p, n, d) - of nkStmtList: - for i in countup(0, sonsLen(n) - 1): genStmts(p, n.sons[i]) + of nkStmtList: genStmtList(p, n) of nkIfExpr, nkIfStmt: genIf(p, n, d) of nkWhen: # This should be a "when nimvm" node. @@ -2268,7 +2323,7 @@ proc getDefaultValue(p: BProc; typ: PType; info: TLineInfo): Rope = of tyBool: result = rope"NIM_FALSE" of tyEnum, tyChar, tyInt..tyInt64, tyUInt..tyUInt64: result = rope"0" of tyFloat..tyFloat128: result = rope"0.0" - of tyCString, tyString, tyVar, tyPointer, tyPtr, tySequence, tyExpr, + of tyCString, tyString, tyVar, tyLent, tyPointer, tyPtr, tySequence, tyExpr, tyStmt, tyTypeDesc, tyStatic, tyRef, tyNil: result = rope"NIM_NIL" of tyProc: diff --git a/compiler/ccgmerge.nim b/compiler/ccgmerge.nim index 58a03ecd2a..f667be70f2 100644 --- a/compiler/ccgmerge.nim +++ b/compiler/ccgmerge.nim @@ -96,7 +96,7 @@ proc writeIntSet(a: IntSet, s: var string) = s.add('}') proc genMergeInfo*(m: BModule): Rope = - if optSymbolFiles notin gGlobalOptions: return nil + if not compilationCachePresent: return nil var s = "/*\tNIM_merge_INFO:" s.add(tnl) s.add("typeCache:{") diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 24a376dece..c9642b74d3 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -21,8 +21,12 @@ proc registerGcRoot(p: BProc, v: PSym) = # we register a specialized marked proc here; this has the advantage # that it works out of the box for thread local storage then :-) let prc = genTraverseProcForGlobal(p.module, v, v.info) - appcg(p.module, p.module.initProc.procSec(cpsInit), - "#nimRegisterGlobalMarker($1);$n", [prc]) + if sfThread in v.flags: + appcg(p.module, p.module.initProc.procSec(cpsInit), + "#nimRegisterThreadLocalMarker($1);$n", [prc]) + else: + appcg(p.module, p.module.initProc.procSec(cpsInit), + "#nimRegisterGlobalMarker($1);$n", [prc]) proc isAssignedImmediately(n: PNode): bool {.inline.} = if n.kind == nkEmpty: return false @@ -564,9 +568,6 @@ proc genBreakStmt(p: BProc, t: PNode) = genLineDir(p, t) lineF(p, cpsStmts, "goto $1;$n", [label]) -proc getRaiseFrmt(p: BProc): string = - result = "#raiseException((#Exception*)$1, $2);$n" - proc genRaiseStmt(p: BProc, t: PNode) = if p.inExceptBlock > 0: # if the current try stmt have a finally block, @@ -580,7 +581,8 @@ proc genRaiseStmt(p: BProc, t: PNode) = var e = rdLoc(a) var typ = skipTypes(t.sons[0].typ, abstractPtrs) genLineDir(p, t) - lineCg(p, cpsStmts, getRaiseFrmt(p), [e, makeCString(typ.sym.name.s)]) + lineCg(p, cpsStmts, "#raiseException((#Exception*)$1, $2);$n", + [e, makeCString(typ.sym.name.s)]) else: genLineDir(p, t) # reraise the last exception: diff --git a/compiler/ccgtrav.nim b/compiler/ccgtrav.nim index 275c2ddb62..ad5d1c95f5 100644 --- a/compiler/ccgtrav.nim +++ b/compiler/ccgtrav.nim @@ -66,7 +66,8 @@ proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, typ: PType) = var p = c.p case typ.kind - of tyGenericInst, tyGenericBody, tyTypeDesc, tyAlias, tyDistinct, tyInferred: + of tyGenericInst, tyGenericBody, tyTypeDesc, tyAlias, tyDistinct, tyInferred, + tySink: genTraverseProc(c, accessor, lastSon(typ)) of tyArray: let arraySize = lengthOrd(typ.sons[0]) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index cfa2afdd95..8a60143fb7 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -119,7 +119,7 @@ proc scopeMangledParam(p: BProc; param: PSym) = const irrelevantForBackend = {tyGenericBody, tyGenericInst, tyGenericInvocation, - tyDistinct, tyRange, tyStatic, tyAlias, tyInferred} + tyDistinct, tyRange, tyStatic, tyAlias, tySink, tyInferred} proc typeName(typ: PType): Rope = let typ = typ.skipTypes(irrelevantForBackend) @@ -139,7 +139,7 @@ proc getTypeName(m: BModule; typ: PType; sig: SigHash): Rope = t = t.lastSon else: break - let typ = if typ.kind == tyAlias: typ.lastSon else: typ + let typ = if typ.kind in {tyAlias, tySink}: typ.lastSon else: typ if typ.loc.r == nil: typ.loc.r = typ.typeName & $sig else: @@ -170,7 +170,7 @@ proc mapType(typ: PType): TCTypeKind = internalAssert typ.isResolvedUserTypeClass return mapType(typ.lastSon) of tyGenericBody, tyGenericInst, tyGenericParam, tyDistinct, tyOrdinal, - tyTypeDesc, tyAlias, tyInferred: + tyTypeDesc, tyAlias, tySink, tyInferred: result = mapType(lastSon(typ)) of tyEnum: if firstOrd(typ) < 0: @@ -183,7 +183,7 @@ proc mapType(typ: PType): TCTypeKind = of 8: result = ctInt64 else: internalError("mapType") of tyRange: result = mapType(typ.sons[0]) - of tyPtr, tyVar, tyRef, tyOptAsRef: + of tyPtr, tyVar, tyLent, tyRef, tyOptAsRef: var base = skipTypes(typ.lastSon, typedescInst) case base.kind of tyOpenArray, tyArray, tyVarargs: result = ctPtrToArray @@ -242,7 +242,7 @@ proc isInvalidReturnType(rettype: PType): bool = case mapType(rettype) of ctArray: result = not (skipTypes(rettype, typedescInst).kind in - {tyVar, tyRef, tyPtr}) + {tyVar, tyLent, tyRef, tyPtr}) of ctStruct: let t = skipTypes(rettype, typedescInst) if rettype.isImportedCppType or t.isImportedCppType: return false @@ -328,7 +328,7 @@ proc getSimpleTypeDesc(m: BModule, typ: PType): Rope = of tyStatic: if typ.n != nil: result = getSimpleTypeDesc(m, lastSon typ) else: internalError("tyStatic for getSimpleTypeDesc") - of tyGenericInst, tyAlias: + of tyGenericInst, tyAlias, tySink: result = getSimpleTypeDesc(m, lastSon typ) else: result = nil @@ -348,7 +348,7 @@ proc getTypePre(m: BModule, typ: PType; sig: SigHash): Rope = if result == nil: result = cacheGetType(m.typeCache, sig) proc structOrUnion(t: PType): Rope = - let t = t.skipTypes({tyAlias}) + let t = t.skipTypes({tyAlias, tySink}) (if tfUnion in t.flags: rope("union") else: rope("struct")) proc getForwardStructFormat(m: BModule): string = @@ -396,7 +396,7 @@ proc getTypeDescWeak(m: BModule; t: PType; check: var IntSet): Rope = result = getTypeDescAux(m, t, check) proc paramStorageLoc(param: PSym): TStorageLoc = - if param.typ.skipTypes({tyVar, tyTypeDesc}).kind notin { + if param.typ.skipTypes({tyVar, tyLent, tyTypeDesc}).kind notin { tyArray, tyOpenArray, tyVarargs}: result = OnStack else: @@ -430,11 +430,11 @@ proc genProcParams(m: BModule, t: PType, rettype, params: var Rope, add(params, param.loc.r) # declare the len field for open arrays: var arr = param.typ - if arr.kind == tyVar: arr = arr.sons[0] + if arr.kind in {tyVar, tyLent}: arr = arr.lastSon var j = 0 while arr.kind in {tyOpenArray, tyVarargs}: # this fixes the 'sort' bug: - if param.typ.kind == tyVar: param.loc.storage = OnUnknown + if param.typ.kind in {tyVar, tyLent}: param.loc.storage = OnUnknown # need to pass hidden parameter: addf(params, ", NI $1Len_$2", [param.loc.r, j.rope]) inc(j) @@ -496,7 +496,7 @@ proc genRecordFieldsAux(m: BModule, n: PNode, if hasAttribute in CC[cCompiler].props: add(unionBody, "struct __attribute__((__packed__)){" ) else: - addf(unionBody, "#pragma pack(1)$nstruct{", []) + addf(unionBody, "#pragma pack(push, 1)$nstruct{", []) add(unionBody, a) addf(unionBody, "} $1;$n", [sname]) if tfPacked in rectype.flags and hasAttribute notin CC[cCompiler].props: @@ -551,7 +551,7 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope, if hasAttribute in CC[cCompiler].props: result = structOrUnion(typ) & " __attribute__((__packed__))" else: - result = "#pragma pack(1)" & tnl & structOrUnion(typ) + result = "#pragma pack(push, 1)" & tnl & structOrUnion(typ) else: result = structOrUnion(typ) @@ -641,7 +641,7 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = excl(check, t.id) return case t.kind - of tyRef, tyOptAsRef, tyPtr, tyVar: + of tyRef, tyOptAsRef, tyPtr, tyVar, tyLent: var star = if t.kind == tyVar and tfVarIsPtr notin origTyp.flags and compileToCpp(m): "&" else: "*" var et = origTyp.skipTypes(abstractInst).lastSon @@ -872,7 +872,7 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = of 1, 2, 4, 8: addf(m.s[cfsTypes], "typedef NU$2 $1;$n", [result, rope(s*8)]) else: addf(m.s[cfsTypes], "typedef NU8 $1[$2];$n", [result, rope(getSize(t))]) - of tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, + of tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, tySink, tyUserTypeClass, tyUserTypeClassInst, tyInferred: result = getTypeDescAux(m, lastSon(t), check) else: @@ -925,6 +925,8 @@ proc genProcHeader(m: BModule, prc: PSym): Rope = result.add "N_LIB_EXPORT " elif prc.typ.callConv == ccInline: result.add "static " + elif {sfImportc, sfExportc} * prc.flags == {}: + result.add "N_LIB_PRIVATE " var check = initIntSet() fillLoc(prc.loc, locProc, prc.ast[namePos], mangleName(m, prc), OnUnknown) genProcParams(m, prc.typ, rettype, params, check) @@ -1225,7 +1227,7 @@ proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope = m.g.typeInfoMarker[sig] = result case t.kind of tyEmpty, tyVoid: result = rope"0" - of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar: + of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar, tyLent: genTypeInfoAuxBase(m, t, t, result, rope"0", info) of tyStatic: if t.n != nil: result = genTypeInfo(m, lastSon t, info) diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim index b1a268c9ee..fe28d2209b 100644 --- a/compiler/ccgutils.nim +++ b/compiler/ccgutils.nim @@ -110,13 +110,13 @@ proc getUniqueType*(key: PType): PType = of tyDistinct: if key.deepCopy != nil: result = key else: result = getUniqueType(lastSon(key)) - of tyGenericInst, tyOrdinal, tyStatic, tyAlias, tyInferred: + of tyGenericInst, tyOrdinal, tyStatic, tyAlias, tySink, tyInferred: result = getUniqueType(lastSon(key)) #let obj = lastSon(key) #if obj.sym != nil and obj.sym.name.s == "TOption": # echo "for ", typeToString(key), " I returned " # debug result - of tyPtr, tyRef, tyVar: + of tyPtr, tyRef, tyVar, tyLent: let elemType = lastSon(key) if elemType.kind in {tyBool, tyChar, tyInt..tyUInt64}: # no canonicalization for integral types, so that e.g. ``ptr pid_t`` is diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 07c2824d0f..630426cfd1 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -493,7 +493,32 @@ proc initLocExprSingleUse(p: BProc, e: PNode, result: var TLoc) = proc lenField(p: BProc): Rope = result = rope(if p.module.compileToCpp: "len" else: "Sup.len") -include ccgcalls, "ccgstmts.nim", "ccgexprs.nim" +include ccgcalls, "ccgstmts.nim" + +proc initFrame(p: BProc, procname, filename: Rope): Rope = + discard cgsym(p.module, "nimFrame") + if p.maxFrameLen > 0: + discard cgsym(p.module, "VarSlot") + result = rfmt(nil, "\tnimfrs_($1, $2, $3, $4);$n", + procname, filename, p.maxFrameLen.rope, + p.blocks[0].frameLen.rope) + else: + result = rfmt(nil, "\tnimfr_($1, $2);$n", procname, filename) + +proc initFrameNoDebug(p: BProc; frame, procname, filename: Rope; line: int): Rope = + discard cgsym(p.module, "nimFrame") + addf(p.blocks[0].sections[cpsLocals], "TFrame $1;$n", [frame]) + result = rfmt(nil, "\t$1.procname = $2; $1.filename = $3; " & + " $1.line = $4; $1.len = -1; nimFrame(&$1);$n", + frame, procname, filename, rope(line)) + +proc deinitFrameNoDebug(p: BProc; frame: Rope): Rope = + result = rfmt(p.module, "\t#popFrameOfAddr(&$1);$n", frame) + +proc deinitFrame(p: BProc): Rope = + result = rfmt(p.module, "\t#popFrame();$n") + +include ccgexprs # ----------------------------- dynamic library handling ----------------- # We don't finalize dynamic libs as the OS does this for us. @@ -600,7 +625,7 @@ proc symInDynamicLibPartial(m: BModule, sym: PSym) = sym.typ.sym = nil # generate a new name proc cgsym(m: BModule, name: string): Rope = - var sym = magicsys.getCompilerProc(name) + let sym = magicsys.getCompilerProc(name) if sym != nil: case sym.kind of skProc, skFunc, skMethod, skConverter, skIterator: genProc(m, sym) @@ -637,19 +662,6 @@ proc generateHeaders(m: BModule) = add(m.s[cfsHeaders], "#undef powerpc" & tnl) add(m.s[cfsHeaders], "#undef unix" & tnl) -proc initFrame(p: BProc, procname, filename: Rope): Rope = - discard cgsym(p.module, "nimFrame") - if p.maxFrameLen > 0: - discard cgsym(p.module, "VarSlot") - result = rfmt(nil, "\tnimfrs_($1, $2, $3, $4);$n", - procname, filename, p.maxFrameLen.rope, - p.blocks[0].frameLen.rope) - else: - result = rfmt(nil, "\tnimfr_($1, $2);$n", procname, filename) - -proc deinitFrame(p: BProc): Rope = - result = rfmt(p.module, "\t#popFrame();$n") - proc closureSetup(p: BProc, prc: PSym) = if tfCapturesEnv notin prc.typ.flags: return # prc.ast[paramsPos].last contains the type we're after: @@ -1246,7 +1258,7 @@ proc resetModule*(m: BModule) = # indicate that this is now cached module # the cache will be invalidated by nullifying gModules - m.fromCache = true + #m.fromCache = true m.g = nil # we keep only the "merge info" information for the module @@ -1324,7 +1336,6 @@ proc getCFile(m: BModule): string = proc myOpenCached(graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext = injectG(graph.config) - assert optSymbolFiles in gGlobalOptions var m = newModule(g, module) readMergeInfo(getCFile(m), m) result = m @@ -1378,7 +1389,7 @@ proc writeModule(m: BModule, pending: bool) = # generate code for the init statements of the module: let cfile = getCFile(m) - if not m.fromCache or optForceFullMake in gGlobalOptions: + if m.rd == nil or optForceFullMake in gGlobalOptions: genInitCode(m) finishTypeDescriptions(m) if sfMainModule in m.module.flags: @@ -1431,6 +1442,10 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = result = n if b == nil or passes.skipCodegen(n): return var m = BModule(b) + # if the module is cached, we don't regenerate the main proc + # nor the dispatchers? But if the dispatchers changed? + # XXX emit the dispatchers into its own .c file? + if b.rd != nil: return if n != nil: m.initProc.options = initProcOptions(m) genStmts(m.initProc, n) @@ -1453,10 +1468,10 @@ proc cgenWriteModules*(backend: RootRef, config: ConfigRef) = if g.generatedHeader != nil: finishModule(g.generatedHeader) while g.forwardedProcsCounter > 0: for m in cgenModules(g): - if not m.fromCache: + if m.rd == nil: finishModule(m) for m in cgenModules(g): - if m.fromCache: + if m.rd != nil: m.updateCachedModule else: m.writeModule(pending=true) diff --git a/compiler/cgmeth.nim b/compiler/cgmeth.nim index 6f7d9f4895..0513e88f42 100644 --- a/compiler/cgmeth.nim +++ b/compiler/cgmeth.nim @@ -68,7 +68,7 @@ proc sameMethodBucket(a, b: PSym): MethodResult = while true: aa = skipTypes(aa, {tyGenericInst, tyAlias}) bb = skipTypes(bb, {tyGenericInst, tyAlias}) - if aa.kind == bb.kind and aa.kind in {tyVar, tyPtr, tyRef}: + if aa.kind == bb.kind and aa.kind in {tyVar, tyPtr, tyRef, tyLent}: aa = aa.lastSon bb = bb.lastSon else: diff --git a/compiler/commands.nim b/compiler/commands.nim index de474c6e68..18b328fd6d 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -261,7 +261,7 @@ proc testCompileOption*(switch: string, info: TLineInfo): bool = of "assertions", "a": result = contains(gOptions, optAssert) of "deadcodeelim": result = contains(gGlobalOptions, optDeadCodeElim) of "run", "r": result = contains(gGlobalOptions, optRun) - of "symbolfiles": result = contains(gGlobalOptions, optSymbolFiles) + of "symbolfiles": result = gSymbolFiles != disabledSf of "genscript": result = contains(gGlobalOptions, optGenScript) of "threads": result = contains(gGlobalOptions, optThreads) of "taintmode": result = contains(gGlobalOptions, optTaintMode) @@ -598,7 +598,13 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; expectNoArg(switch, arg, pass, info) helpOnError(pass) of "symbolfiles": - processOnOffSwitchG({optSymbolFiles}, arg, pass, info) + case arg.normalize + of "on": gSymbolFiles = enabledSf + of "off": gSymbolFiles = disabledSf + of "writeonly": gSymbolFiles = writeOnlySf + of "readonly": gSymbolFiles = readOnlySf + of "v2": gSymbolFiles = v2Sf + else: localError(info, errOnOrOffExpectedButXFound, arg) of "skipcfg": expectNoArg(switch, arg, pass, info) incl(gGlobalOptions, optSkipConfigFile) @@ -611,7 +617,7 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; of "skipparentcfg": expectNoArg(switch, arg, pass, info) incl(gGlobalOptions, optSkipParentConfigFiles) - of "genscript": + of "genscript", "gendeps": expectNoArg(switch, arg, pass, info) incl(gGlobalOptions, optGenScript) of "colors": processOnOffSwitchG({optUseColors}, arg, pass, info) diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index a52214e734..0be2899be0 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -112,3 +112,4 @@ proc initDefines*() = defineSymbol("nimNewRoof") defineSymbol("nimHasRunnableExamples") defineSymbol("nimNewDot") + defineSymbol("nimHasNilChecks") diff --git a/compiler/destroyer.nim b/compiler/destroyer.nim index caa18af92f..55da69985a 100644 --- a/compiler/destroyer.nim +++ b/compiler/destroyer.nim @@ -174,7 +174,7 @@ proc patchHead(n: PNode) = if n[1].typ.isNil: # XXX toptree crashes without this workaround. Figure out why. return - let t = n[1].typ.skipTypes({tyVar, tyGenericInst, tyAlias, tyInferred}) + let t = n[1].typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink, tyInferred}) template patch(op, field) = if s.name.s == op and field != nil and field != s: n.sons[0].sym = field @@ -198,15 +198,15 @@ template genOp(opr, opname) = result = newTree(nkCall, newSymNode(op), newTree(nkHiddenAddr, dest)) proc genSink(t: PType; dest: PNode): PNode = - let t = t.skipTypes({tyGenericInst, tyAlias}) + let t = t.skipTypes({tyGenericInst, tyAlias, tySink}) genOp(if t.sink != nil: t.sink else: t.assignment, "=sink") proc genCopy(t: PType; dest: PNode): PNode = - let t = t.skipTypes({tyGenericInst, tyAlias}) + let t = t.skipTypes({tyGenericInst, tyAlias, tySink}) genOp(t.assignment, "=") proc genDestroy(t: PType; dest: PNode): PNode = - let t = t.skipTypes({tyGenericInst, tyAlias}) + let t = t.skipTypes({tyGenericInst, tyAlias, tySink}) genOp(t.destructor, "=destroy") proc addTopVar(c: var Con; v: PNode) = @@ -296,7 +296,8 @@ proc p(n: PNode; c: var Con): PNode = recurse(n, result) proc injectDestructorCalls*(owner: PSym; n: PNode): PNode = - echo "injecting into ", n + when defined(nimDebugDestroys): + echo "injecting into ", n var c: Con c.owner = owner c.tmp = newSym(skTemp, getIdent":d", owner, n.info) diff --git a/compiler/dfa.nim b/compiler/dfa.nim index 22a110a1f4..b648995f42 100644 --- a/compiler/dfa.nim +++ b/compiler/dfa.nim @@ -132,7 +132,7 @@ proc gen(c: var Con; n: PNode) # {.noSideEffect.} proc genWhile(c: var Con; n: PNode) = # L1: # cond, tmp - # fjmp tmp, L2 + # fork tmp, L2 # body # jmp L1 # L2: @@ -168,15 +168,13 @@ proc genIf(c: var Con, n: PNode) = var endings: seq[TPosition] = @[] for i in countup(0, len(n) - 1): var it = n.sons[i] + c.gen(it.sons[0]) if it.len == 2: - c.gen(it.sons[0].sons[1]) - var elsePos = c.forkI(it.sons[0].sons[1]) + let elsePos = c.forkI(it.sons[1]) c.gen(it.sons[1]) if i < sonsLen(n)-1: endings.add(c.gotoI(it.sons[1])) c.patch(elsePos) - else: - c.gen(it.sons[0]) for endPos in endings: c.patch(endPos) proc genAndOr(c: var Con; n: PNode) = @@ -337,100 +335,107 @@ proc gen(c: var Con; n: PNode) = else: discard proc dfa(code: seq[Instr]) = - # We aggressively push 'undef' values for every 'use v' instruction - # until they are eliminated via a 'def v' instructions. - # If we manage to push one 'undef' to a 'use' instruction, we produce - # an error: - var undef = initIntSet() + var u = newSeq[IntSet](code.len) # usages + var d = newSeq[IntSet](code.len) # defs + var c = newSeq[IntSet](code.len) # consumed + var backrefs = initTable[int, int]() for i in 0.. 0: - var pc = w[^1] + var maxIters = 50 + var someChange = true + var takenGotos = initIntSet() + var consuming = -1 + while w.len > 0 and maxIters > 0: # and someChange: + dec maxIters + var pc = w.pop() # w[^1] + var prevPc = -1 # this simulates a single linear control flow execution: - while true: - # according to the paper, it is better to shrink the working set here - # in this inner loop: - let widx = w.find(pc) - if widx >= 0: w.del(widx) + while pc < code.len: + if prevPc >= 0: + someChange = false + # merge step and test for changes (we compute the fixpoints here): + # 'u' needs to be the union of prevPc, pc + # 'd' needs to be the intersection of 'pc' + for id in u[prevPc]: + if not u[pc].containsOrIncl(id): + someChange = true + # in (a; b) if ``a`` sets ``v`` so does ``b``. The intersection + # is only interesting on merge points: + for id in d[prevPc]: + if not d[pc].containsOrIncl(id): + someChange = true + # if this is a merge point, we take the intersection of the 'd' sets: + if backrefs.hasKey(pc): + var intersect = initIntSet() + assign(intersect, d[pc]) + var first = true + for prevPc in backrefs.allValues(pc): + for def in d[pc]: + if def notin d[prevPc]: + excl(intersect, def) + someChange = true + when defined(debugDfa): + echo "Excluding ", pc, " prev ", prevPc + assign d[pc], intersect + if consuming >= 0: + if not c[pc].containsOrIncl(consuming): + someChange = true + consuming = -1 + # our interpretation ![I!]: - var sid = -1 + prevPc = pc case code[pc].kind - of goto, fork: discard - of use, useWithinCall: - let sym = code[pc].sym - if s[pc].contains(sym.id): - localError(code[pc].n.info, "variable read before initialized: " & sym.name.s) - of def: - sid = code[pc].sym.id - - var pc2: int - if code[pc].kind == goto: - pc2 = pc + code[pc].dest - else: - pc2 = pc + 1 - if code[pc].kind == fork: - let l = pc + code[pc].dest - if sid >= 0 and s[l].missingOrExcl(sid): - w.add l - - if sid >= 0 and s[pc2].missingOrExcl(sid): - pc = pc2 - else: - break - if pc >= code.len: break - - when false: - case code[pc].kind - of use: - let s = code[pc].sym - if undefB.contains(s.id): - localError(code[pc].n.info, "variable read before initialized: " & s.name.s) - break - inc pc - of def: - let s = code[pc].sym - # exclude 'undef' for s for this path through the graph. - if not undefB.missingOrExcl(s.id): - inc pc - else: - break - #undefB.excl s.id - #inc pc - when false: - let prev = bindings.getOrDefault(s.id) - if prev != value: - # well now it has a value and we made progress, so - bindings[s.id] = value - inc pc - else: - break - of fork: - let diff = code[pc].dest - # we follow pc + 1 and remember the label for later: - w.add pc+diff - inc pc of goto: - let diff = code[pc].dest - pc = pc + diff - if pc >= code.len: break + # we must leave endless loops eventually: + if not takenGotos.containsOrIncl(pc) or someChange: + pc = pc + code[pc].dest + else: + inc pc + of fork: + # we follow the next instruction but push the dest onto our "work" stack: + #if someChange: + w.add pc + code[pc].dest + inc pc + of use, useWithinCall: + #if not d[prevPc].missingOrExcl(): + # someChange = true + consuming = code[pc].sym.id + inc pc + of def: + if not d[pc].containsOrIncl(code[pc].sym.id): + someChange = true + inc pc + + when defined(useDfa) and defined(debugDfa): + for i in 0.. 0: + localError(n.info, "invalid control flow: 'yield' within a constructor") let state = getStateField(owner) assert state != nil assert state.typ != nil @@ -703,11 +706,14 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass; if not c.processed.containsOrIncl(s.id): #if s.name.s == "temp": # echo renderTree(s.getBody, {renderIds}) + let oldInContainer = c.inContainer + c.inContainer = 0 let body = wrapIterBody(liftCapturedVars(s.getBody, s, d, c), s) if c.envvars.getOrDefault(s.id).isNil: s.ast.sons[bodyPos] = body else: s.ast.sons[bodyPos] = newTree(nkStmtList, rawClosureCreation(s, d, c), body) + c.inContainer = oldInContainer if s.typ.callConv == ccClosure: result = symToClosure(n, owner, d, c) elif s.id in d.capturedVars: @@ -717,7 +723,7 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass; result = accessViaEnvParam(n, owner) else: result = accessViaEnvVar(n, owner, d, c) - of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, + of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, nkComesFrom, nkTemplateDef, nkTypeSection: discard of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef: @@ -733,9 +739,12 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass; n.sons[1] = x.sons[1] of nkLambdaKinds, nkIteratorDef, nkFuncDef: if n.typ != nil and n[namePos].kind == nkSym: + let oldInContainer = c.inContainer + c.inContainer = 0 let m = newSymNode(n[namePos].sym) m.typ = n.typ result = liftCapturedVars(m, owner, d, c) + c.inContainer = oldInContainer of nkHiddenStdConv: if n.len == 2: n.sons[1] = liftCapturedVars(n[1], owner, d, c) @@ -750,8 +759,12 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass; # special case 'when nimVm' due to bug #3636: n.sons[1] = liftCapturedVars(n[1], owner, d, c) return + + let inContainer = n.kind in {nkObjConstr, nkBracket} + if inContainer: inc c.inContainer for i in 0.. 0 and ($ver)[0] == '#' +proc isValidVersion(v: string): bool = + if v.len > 0: + if v[0] in {'#'} + Digits: return true + proc `<`*(ver: Version, ver2: Version): bool = ## This is synced from Nimble's version module. @@ -72,15 +76,23 @@ proc getPathVersion*(p: string): tuple[name, version: string] = result.name = p return + for i in sepIdx..= gcRefc template preciseStack*(): bool = gPreciseStack template compilationCachePresent*: untyped = - {optCaasEnabled, optSymbolFiles} * gGlobalOptions != {} + gSymbolFiles in {enabledSf, writeOnlySf} +# {optCaasEnabled, optSymbolFiles} * gGlobalOptions != {} template optPreserveOrigSource*: untyped = optEmbedOrigSrc in gGlobalOptions diff --git a/compiler/passes.nim b/compiler/passes.nim index b84fe2f4d7..29b27627d3 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -18,7 +18,7 @@ import type TPassContext* = object of RootObj # the pass's context - fromCache*: bool # true if created by "openCached" + rd*: PRodReader # != nil if created by "openCached" PPassContext* = ref TPassContext @@ -118,7 +118,7 @@ proc openPassesCached(g: ModuleGraph; a: var TPassContextArray, module: PSym, if not isNil(gPasses[i].openCached): a[i] = gPasses[i].openCached(g, module, rd) if a[i] != nil: - a[i].fromCache = true + a[i].rd = rd else: a[i] = nil diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index b598cadb20..b6229796fa 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -17,11 +17,12 @@ import const FirstCallConv* = wNimcall LastCallConv* = wNoconv + nkPragmaCallKinds = {nkExprColonExpr, nkCall, nkCallStrLit} const procPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl, wMagic, wNosideeffect, wSideeffect, wNoreturn, wDynlib, wHeader, - wCompilerproc, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge, + wCompilerProc, wCore, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge, wBorrow, wExtern, wImportCompilerProc, wThread, wImportCpp, wImportObjC, wAsmNoStackFrame, wError, wDiscardable, wNoInit, wCodegenDecl, wGensym, wInject, wRaises, wTags, wLocks, wDelegator, wGcSafe, @@ -29,9 +30,9 @@ const converterPragmas* = procPragmas methodPragmas* = procPragmas+{wBase}-{wImportCpp} templatePragmas* = {wImmediate, wDeprecated, wError, wGensym, wInject, wDirty, - wDelegator, wExportNims, wUsed} + wDelegator, wExportNims, wUsed, wPragma} macroPragmas* = {FirstCallConv..LastCallConv, wImmediate, wImportc, wExportc, - wNodecl, wMagic, wNosideeffect, wCompilerproc, wDeprecated, wExtern, + wNodecl, wMagic, wNosideeffect, wCompilerProc, wCore, wDeprecated, wExtern, wImportCpp, wImportObjC, wError, wDiscardable, wGensym, wInject, wDelegator, wExportNims, wUsed} iteratorPragmas* = {FirstCallConv..LastCallConv, wNosideeffect, wSideeffect, @@ -52,14 +53,14 @@ const wDeprecated, wExtern, wThread, wImportCpp, wImportObjC, wAsmNoStackFrame, wRaises, wLocks, wTags, wGcSafe} typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl, - wPure, wHeader, wCompilerproc, wFinal, wSize, wExtern, wShallow, + wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wExtern, wShallow, wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef, wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked, wBorrow, wGcSafe, wExportNims, wPartial, wUsed, wExplain, wPackage} fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern, wImportCpp, wImportObjC, wError, wGuard, wBitsize, wUsed} varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl, - wMagic, wHeader, wDeprecated, wCompilerproc, wDynlib, wExtern, + wMagic, wHeader, wDeprecated, wCompilerProc, wCore, wDynlib, wExtern, wImportCpp, wImportObjC, wError, wNoInit, wCompileTime, wGlobal, wGensym, wInject, wCodegenDecl, wGuard, wGoto, wExportNims, wUsed} constPragmas* = {wImportc, wExportc, wHeader, wDeprecated, wMagic, wNodecl, @@ -74,7 +75,7 @@ proc getPragmaVal*(procAst: PNode; name: TSpecialWord): PNode = let p = procAst[pragmasPos] if p.kind == nkEmpty: return nil for it in p: - if it.kind == nkExprColonExpr and it[0].kind == nkIdent and + if it.kind in nkPragmaCallKinds and it.len == 2 and it[0].kind == nkIdent and it[0].ident.id == ord(name): return it[1] @@ -89,7 +90,7 @@ proc pragmaAsm*(c: PContext, n: PNode): char = if n != nil: for i in countup(0, sonsLen(n) - 1): let it = n.sons[i] - if it.kind == nkExprColonExpr and it.sons[0].kind == nkIdent: + if it.kind in nkPragmaCallKinds and it.len == 2 and it.sons[0].kind == nkIdent: case whichKeyword(it.sons[0].ident) of wSubsChar: if it.sons[1].kind == nkCharLit: result = chr(int(it.sons[1].intVal)) @@ -151,7 +152,7 @@ proc newEmptyStrNode(n: PNode): PNode {.noinline.} = result.strVal = "" proc getStrLitNode(c: PContext, n: PNode): PNode = - if n.kind != nkExprColonExpr: + if n.kind notin nkPragmaCallKinds or n.len != 2: localError(n.info, errStringLiteralExpected) # error correction: result = newEmptyStrNode(n) @@ -168,7 +169,7 @@ proc expectStrLit(c: PContext, n: PNode): string = result = getStrLitNode(c, n).strVal proc expectIntLit(c: PContext, n: PNode): int = - if n.kind != nkExprColonExpr: + if n.kind notin nkPragmaCallKinds or n.len != 2: localError(n.info, errIntLiteralExpected) else: n.sons[1] = c.semConstExpr(c, n.sons[1]) @@ -177,7 +178,7 @@ proc expectIntLit(c: PContext, n: PNode): int = else: localError(n.info, errIntLiteralExpected) proc getOptionalStr(c: PContext, n: PNode, defaultStr: string): string = - if n.kind == nkExprColonExpr: result = expectStrLit(c, n) + if n.kind in nkPragmaCallKinds: result = expectStrLit(c, n) else: result = defaultStr proc processCodegenDecl(c: PContext, n: PNode, sym: PSym) = @@ -186,7 +187,7 @@ proc processCodegenDecl(c: PContext, n: PNode, sym: PSym) = proc processMagic(c: PContext, n: PNode, s: PSym) = #if sfSystemModule notin c.module.flags: # liMessage(n.info, errMagicOnlyInSystem) - if n.kind != nkExprColonExpr: + if n.kind notin nkPragmaCallKinds or n.len != 2: localError(n.info, errStringLiteralExpected) return var v: string @@ -204,7 +205,7 @@ proc wordToCallConv(sw: TSpecialWord): TCallingConvention = result = TCallingConvention(ord(ccDefault) + ord(sw) - ord(wNimcall)) proc isTurnedOn(c: PContext, n: PNode): bool = - if n.kind == nkExprColonExpr: + if n.kind in nkPragmaCallKinds and n.len == 2: let x = c.semConstBoolExpr(c, n.sons[1]) n.sons[1] = x if x.kind == nkIntLit: return x.intVal != 0 @@ -223,7 +224,7 @@ proc pragmaNoForward(c: PContext, n: PNode; flag=sfNoForward) = else: excl(c.module.flags, flag) proc processCallConv(c: PContext, n: PNode) = - if (n.kind == nkExprColonExpr) and (n.sons[1].kind == nkIdent): + if n.kind in nkPragmaCallKinds and n.len == 2 and n.sons[1].kind == nkIdent: var sw = whichKeyword(n.sons[1].ident) case sw of FirstCallConv..LastCallConv: @@ -244,7 +245,7 @@ proc getLib(c: PContext, kind: TLibKind, path: PNode): PLib = result.isOverriden = options.isDynlibOverride(path.strVal) proc expectDynlibNode(c: PContext, n: PNode): PNode = - if n.kind != nkExprColonExpr: + if n.kind notin nkPragmaCallKinds or n.len != 2: localError(n.info, errStringLiteralExpected) # error correction: result = newEmptyStrNode(n) @@ -264,7 +265,7 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) = if not lib.isOverriden: c.optionStack[^1].dynlib = lib else: - if n.kind == nkExprColonExpr: + if n.kind in nkPragmaCallKinds: var lib = getLib(c, libDynamic, expectDynlibNode(c, n)) if not lib.isOverriden: addToLib(lib, sym) @@ -279,7 +280,7 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) = sym.typ.callConv = ccCDecl proc processNote(c: PContext, n: PNode) = - if (n.kind == nkExprColonExpr) and (sonsLen(n) == 2) and + if (n.kind in nkPragmaCallKinds) and (sonsLen(n) == 2) and (n.sons[0].kind == nkBracketExpr) and (n.sons[0].sons.len == 2) and (n.sons[0].sons[1].kind == nkIdent) and @@ -307,7 +308,7 @@ proc processNote(c: PContext, n: PNode) = invalidPragma(n) proc processOption(c: PContext, n: PNode): bool = - if n.kind != nkExprColonExpr: result = true + if n.kind notin nkPragmaCallKinds or n.len != 2: result = true elif n.sons[0].kind == nkBracketExpr: processNote(c, n) elif n.sons[0].kind != nkIdent: result = true else: @@ -355,8 +356,8 @@ proc processOption(c: PContext, n: PNode): bool = else: result = true proc processPush(c: PContext, n: PNode, start: int) = - if n.sons[start-1].kind == nkExprColonExpr: - localError(n.info, errGenerated, "':' after 'push' not supported") + if n.sons[start-1].kind in nkPragmaCallKinds: + localError(n.info, errGenerated, "'push' can't have arguments") var x = newOptionEntry() var y = c.optionStack[^1] x.options = gOptions @@ -381,14 +382,14 @@ proc processPop(c: PContext, n: PNode) = c.optionStack.setLen(c.optionStack.len - 1) proc processDefine(c: PContext, n: PNode) = - if (n.kind == nkExprColonExpr) and (n.sons[1].kind == nkIdent): + if (n.kind in nkPragmaCallKinds and n.len == 2) and (n.sons[1].kind == nkIdent): defineSymbol(n.sons[1].ident.s) message(n.info, warnDeprecated, "define") else: invalidPragma(n) proc processUndef(c: PContext, n: PNode) = - if (n.kind == nkExprColonExpr) and (n.sons[1].kind == nkIdent): + if (n.kind in nkPragmaCallKinds and n.len == 2) and (n.sons[1].kind == nkIdent): undefSymbol(n.sons[1].ident.s) message(n.info, warnDeprecated, "undef") else: @@ -420,7 +421,7 @@ proc processCompile(c: PContext, n: PNode) = localError(n.info, errStringLiteralExpected) result = "" - let it = if n.kind == nkExprColonExpr: n.sons[1] else: n + let it = if n.kind in nkPragmaCallKinds and n.len == 2: n.sons[1] else: n if it.kind == nkPar and it.len == 2: let s = getStrLit(c, it, 0) let dest = getStrLit(c, it, 1) @@ -453,7 +454,7 @@ proc pragmaBreakpoint(c: PContext, n: PNode) = discard getOptionalStr(c, n, "") proc pragmaWatchpoint(c: PContext, n: PNode) = - if n.kind == nkExprColonExpr: + if n.kind in nkPragmaCallKinds and n.len == 2: n.sons[1] = c.semExpr(c, n.sons[1]) else: invalidPragma(n) @@ -494,7 +495,7 @@ proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode = result = newNode(nkAsmStmt, n.info) proc pragmaEmit(c: PContext, n: PNode) = - if n.kind != nkExprColonExpr: + if n.kind notin nkPragmaCallKinds or n.len != 2: localError(n.info, errStringLiteralExpected) else: let n1 = n[1] @@ -512,12 +513,12 @@ proc pragmaEmit(c: PContext, n: PNode) = localError(n.info, errStringLiteralExpected) proc noVal(n: PNode) = - if n.kind == nkExprColonExpr: invalidPragma(n) + if n.kind in nkPragmaCallKinds and n.len > 1: invalidPragma(n) proc pragmaUnroll(c: PContext, n: PNode) = if c.p.nestedLoopCounter <= 0: invalidPragma(n) - elif n.kind == nkExprColonExpr: + elif n.kind in nkPragmaCallKinds and n.len == 2: var unrollFactor = expectIntLit(c, n) if unrollFactor <% 32: n.sons[1] = newIntNode(nkIntLit, unrollFactor) @@ -525,10 +526,11 @@ proc pragmaUnroll(c: PContext, n: PNode) = invalidPragma(n) proc pragmaLine(c: PContext, n: PNode) = - if n.kind == nkExprColonExpr: + if n.kind in nkPragmaCallKinds and n.len == 2: n.sons[1] = c.semConstExpr(c, n.sons[1]) let a = n.sons[1] if a.kind == nkPar: + # unpack the tuple var x = a.sons[0] var y = a.sons[1] if x.kind == nkExprColonExpr: x = x.sons[1] @@ -549,7 +551,7 @@ proc pragmaLine(c: PContext, n: PNode) = proc processPragma(c: PContext, n: PNode, i: int) = var it = n.sons[i] - if it.kind != nkExprColonExpr: invalidPragma(n) + if it.kind notin nkPragmaCallKinds and it.len == 2: invalidPragma(n) elif it.sons[0].kind != nkIdent: invalidPragma(n) elif it.sons[1].kind != nkIdent: invalidPragma(n) @@ -566,7 +568,7 @@ proc pragmaRaisesOrTags(c: PContext, n: PNode) = localError(x.info, errGenerated, "invalid type for raises/tags list") x.typ = t - if n.kind == nkExprColonExpr: + if n.kind in nkPragmaCallKinds and n.len == 2: let it = n.sons[1] if it.kind notin {nkCurly, nkBracket}: processExc(c, it) @@ -576,7 +578,7 @@ proc pragmaRaisesOrTags(c: PContext, n: PNode) = invalidPragma(n) proc pragmaLockStmt(c: PContext; it: PNode) = - if it.kind != nkExprColonExpr: + if it.kind notin nkPragmaCallKinds or it.len != 2: invalidPragma(it) else: let n = it[1] @@ -587,7 +589,7 @@ proc pragmaLockStmt(c: PContext; it: PNode) = n.sons[i] = c.semExpr(c, n.sons[i]) proc pragmaLocks(c: PContext, it: PNode): TLockLevel = - if it.kind != nkExprColonExpr: + if it.kind notin nkPragmaCallKinds or it.len != 2: invalidPragma(it) else: case it[1].kind @@ -604,7 +606,7 @@ proc pragmaLocks(c: PContext, it: PNode): TLockLevel = result = TLockLevel(x) proc typeBorrow(sym: PSym, n: PNode) = - if n.kind == nkExprColonExpr: + if n.kind in nkPragmaCallKinds and n.len == 2: let it = n.sons[1] if it.kind != nkAccQuoted: localError(n.info, "a type can only borrow `.` for now") @@ -624,7 +626,7 @@ proc deprecatedStmt(c: PContext; pragma: PNode) = if pragma.kind != nkBracket: localError(pragma.info, "list of key:value pairs expected"); return for n in pragma: - if n.kind in {nkExprColonExpr, nkExprEqExpr}: + if n.kind in nkPragmaCallKinds and n.len == 2: let dest = qualifiedLookUp(c, n[1], {checkUndeclared}) if dest == nil or dest.kind in routineKinds: localError(n.info, warnUser, "the .deprecated pragma is unreliable for routines") @@ -638,7 +640,7 @@ proc deprecatedStmt(c: PContext; pragma: PNode) = localError(n.info, "key:value pair expected") proc pragmaGuard(c: PContext; it: PNode; kind: TSymKind): PSym = - if it.kind != nkExprColonExpr: + if it.kind notin nkPragmaCallKinds or it.len != 2: invalidPragma(it); return let n = it[1] if n.kind == nkSym: @@ -655,13 +657,36 @@ proc pragmaGuard(c: PContext; it: PNode; kind: TSymKind): PSym = else: result = qualifiedLookUp(c, n, {checkUndeclared}) +proc semCustomPragma(c: PContext, n: PNode): PNode = + assert(n.kind in nkPragmaCallKinds + {nkIdent}) + + if n.kind == nkIdent: + result = newTree(nkCall, n) + elif n.kind == nkExprColonExpr: + # pragma: arg -> pragma(arg) + result = newTree(nkCall, n[0], n[1]) + else: + result = n + + result = c.semOverloadedCall(c, result, n, {skTemplate}, {}) + if sfCustomPragma notin result[0].sym.flags: + invalidPragma(n) + + if n.kind == nkIdent: + result = result[0] + elif n.kind == nkExprColonExpr: + result.kind = n.kind # pragma(arg) -> pragma: arg + proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, validPragmas: TSpecialWords): bool = var it = n.sons[i] - var key = if it.kind == nkExprColonExpr: it.sons[0] else: it + var key = if it.kind in nkPragmaCallKinds and it.len > 1: it.sons[0] else: it if key.kind == nkBracketExpr: processNote(c, it) return + elif key.kind notin nkIdentKinds: + n.sons[i] = semCustomPragma(c, it) + return let ident = considerQuotedIdent(key) var userPragma = strTableGet(c.userPragmas, ident) if userPragma != nil: @@ -771,9 +796,11 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, of wNoreturn: noVal(it) incl(sym.flags, sfNoReturn) + if sym.typ[0] != nil: + localError(sym.ast[paramsPos][0].info, errNoReturnWithReturnTypeNotAllowed) of wDynlib: processDynLib(c, it, sym) - of wCompilerproc: + of wCompilerProc, wCore: noVal(it) # compilerproc may not get a string! cppDefine(c.graph.config, sym.name.s) if sfFromGeneric notin sym.flags: markCompilerProc(sym) @@ -783,7 +810,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, of wExplain: sym.flags.incl sfExplain of wDeprecated: - if it.kind == nkExprColonExpr: deprecatedStmt(c, it) + if it.kind in nkPragmaCallKinds: deprecatedStmt(c, it) elif sym != nil: incl(sym.flags, sfDeprecated) else: incl(c.module.flags, sfDeprecated) of wVarargs: @@ -862,8 +889,11 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, result = true of wPop: processPop(c, it) of wPragma: - processPragma(c, n, i) - result = true + if not sym.isNil and sym.kind == skTemplate: + sym.flags.incl sfCustomPragma + else: + processPragma(c, n, i) + result = true of wDiscardable: noVal(it) if sym != nil: incl(sym.flags, sfDiscardable) @@ -937,7 +967,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, elif sym.typ == nil: invalidPragma(it) else: sym.typ.lockLevel = pragmaLocks(c, it) of wBitsize: - if sym == nil or sym.kind != skField or it.kind != nkExprColonExpr: + if sym == nil or sym.kind != skField: invalidPragma(it) else: sym.bitsize = expectIntLit(c, it) @@ -955,7 +985,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, if sym == nil: invalidPragma(it) else: magicsys.registerNimScriptSymbol(sym) of wInjectStmt: - if it.kind != nkExprColonExpr: + if it.kind notin nkPragmaCallKinds or it.len != 2: localError(it.info, errExprExpected) else: it.sons[1] = c.semExpr(c, it.sons[1]) @@ -966,10 +996,12 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, else: localError(it.info, "'experimental' pragma only valid as toplevel statement") of wThis: - if it.kind == nkExprColonExpr: + if it.kind in nkPragmaCallKinds and it.len == 2: c.selfName = considerQuotedIdent(it[1]) - else: + elif it.kind == nkIdent or it.len == 1: c.selfName = getIdent("self") + else: + localError(it.info, "'this' pragma is allowed to have zero or one arguments") of wNoRewrite: noVal(it) of wBase: @@ -985,7 +1017,9 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, else: sym.flags.incl sfUsed of wLiftLocals: discard else: invalidPragma(it) - else: invalidPragma(it) + else: + n.sons[i] = semCustomPragma(c, it) + proc implicitPragmas*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) = @@ -1013,7 +1047,7 @@ proc hasPragma*(n: PNode, pragma: TSpecialWord): bool = return false for p in n.sons: - var key = if p.kind == nkExprColonExpr: p[0] else: p + var key = if p.kind in nkPragmaCallKinds and p.len > 1: p[0] else: p if key.kind == nkIdent and whichKeyword(key.ident) == pragma: return true diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 6f80afefa7..fa93c968af 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -175,8 +175,17 @@ proc put(g: var TSrcGen, kind: TTokType, s: string) = proc toNimChar(c: char): string = case c - of '\0': result = "\\0" - of '\x01'..'\x1F', '\x80'..'\xFF': result = "\\x" & strutils.toHex(ord(c), 2) + of '\0': result = "\\x00" # not "\\0" to avoid ambiguous cases like "\\012". + of '\a': result = "\\a" # \x07 + of '\b': result = "\\b" # \x08 + of '\t': result = "\\t" # \x09 + of '\L': result = "\\L" # \x0A + of '\v': result = "\\v" # \x0B + of '\f': result = "\\f" # \x0C + of '\c': result = "\\c" # \x0D + of '\e': result = "\\e" # \x1B + of '\x01'..'\x06', '\x0E'..'\x1A', '\x1C'..'\x1F', '\x80'..'\xFF': + result = "\\x" & strutils.toHex(ord(c), 2) of '\'', '\"', '\\': result = '\\' & c else: result = c & "" @@ -316,8 +325,8 @@ proc lsub(g: TSrcGen; n: PNode): int proc litAux(g: TSrcGen; n: PNode, x: BiggestInt, size: int): string = proc skip(t: PType): PType = result = t - while result.kind in {tyGenericInst, tyRange, tyVar, tyDistinct, - tyOrdinal, tyAlias}: + while result.kind in {tyGenericInst, tyRange, tyVar, tyLent, tyDistinct, + tyOrdinal, tyAlias, tySink}: result = lastSon(result) if n.typ != nil and n.typ.skip.kind in {tyBool, tyEnum}: let enumfields = n.typ.skip.n @@ -710,6 +719,7 @@ proc gcase(g: var TSrcGen, n: PNode) = var c: TContext initContext(c) var length = sonsLen(n) + if length == 0: return var last = if n.sons[length-1].kind == nkElse: -2 else: -1 if longMode(g, n, 0, last): incl(c.flags, rfLongMode) putWithSpace(g, tkCase, "case") @@ -860,7 +870,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = a: TContext if n.comment != nil: pushCom(g, n) case n.kind # atoms: - of nkTripleStrLit: putRawStr(g, tkTripleStrLit, n.strVal) + of nkTripleStrLit: put(g, tkTripleStrLit, atom(g, n)) of nkEmpty: discard of nkType: put(g, tkInvalid, atom(g, n)) of nkSym, nkIdent: gident(g, n) @@ -888,6 +898,14 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = put(g, tkBracketLe, "[") gcomma(g, n, 2) put(g, tkBracketRi, "]") + elif n.len > 1 and n.lastSon.kind == nkStmtList: + gsub(g, n[0]) + if n.len > 2: + put(g, tkParLe, "(") + gcomma(g, n, 1, -2) + put(g, tkParRi, ")") + put(g, tkColon, ":") + gsub(g, n, n.len-1) else: if sonsLen(n) >= 1: gsub(g, n.sons[0]) put(g, tkParLe, "(") @@ -1397,8 +1415,8 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = put(g, tkBracketRi, "]") of nkTupleClassTy: put(g, tkTuple, "tuple") - of nkMetaNode_Obsolete: - put(g, tkParLe, "(META|") + of nkComesFrom: + put(g, tkParLe, "(ComesFrom|") gsub(g, n, 0) put(g, tkParRi, ")") of nkGotoState, nkState: diff --git a/compiler/rodread.nim b/compiler/rodread.nim index 83765c1b73..dfa8fc52ba 100644 --- a/compiler/rodread.nim +++ b/compiler/rodread.nim @@ -861,12 +861,11 @@ proc loadMethods(r: PRodReader) = if r.s[r.pos] == ' ': inc(r.pos) proc getHash*(fileIdx: int32): SecureHash = - internalAssert fileIdx >= 0 and fileIdx < gMods.len - - if gMods[fileIdx].hashDone: + if fileIdx <% gMods.len and gMods[fileIdx].hashDone: return gMods[fileIdx].hash result = secureHashFile(fileIdx.toFullPath) + if fileIdx >= gMods.len: setLen(gMods, fileIdx+1) gMods[fileIdx].hash = result template growCache*(cache, pos) = @@ -912,7 +911,7 @@ proc checkDep(fileIdx: int32; cache: IdentCache): TReasonForRecompile = proc handleSymbolFile*(module: PSym; cache: IdentCache): PRodReader = let fileIdx = module.fileIdx - if optSymbolFiles notin gGlobalOptions: + if gSymbolFiles in {disabledSf, writeOnlySf}: module.id = getID() return nil idgen.loadMaxIds(options.gProjectPath / options.gProjectName) diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim index dac2672636..8eb76457cc 100644 --- a/compiler/scriptconfig.nim +++ b/compiler/scriptconfig.nim @@ -73,7 +73,8 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; cbos copyFile: os.copyFile(getString(a, 0), getString(a, 1)) cbos getLastModificationTime: - setResult(a, toSeconds(getLastModificationTime(getString(a, 0)))) + # depends on Time's implementation! + setResult(a, int64(getLastModificationTime(getString(a, 0)))) cbos rawExec: setResult(a, osproc.execCmd getString(a, 0)) diff --git a/compiler/sem.nim b/compiler/sem.nim index 495321de45..0e97a66b2e 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -74,7 +74,7 @@ proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode = localError(arg.info, errExprXHasNoType, renderTree(arg, {renderNoComments})) # error correction: - result = copyNode(arg) + result = copyTree(arg) result.typ = formal else: result = indexTypesMatch(c, formal, arg.typ, arg) @@ -102,8 +102,8 @@ proc commonType*(x, y: PType): PType = # if expressions, etc.: if x == nil: return x if y == nil: return y - var a = skipTypes(x, {tyGenericInst, tyAlias}) - var b = skipTypes(y, {tyGenericInst, tyAlias}) + var a = skipTypes(x, {tyGenericInst, tyAlias, tySink}) + var b = skipTypes(y, {tyGenericInst, tyAlias, tySink}) result = x if a.kind in {tyExpr, tyNil}: result = y elif b.kind in {tyExpr, tyNil}: result = x @@ -165,6 +165,19 @@ proc commonType*(x, y: PType): PType = result = newType(k, r.owner) result.addSonSkipIntLit(r) +proc endsInNoReturn(n: PNode): bool = + # check if expr ends in raise exception or call of noreturn proc + var it = n + while it.kind in {nkStmtList, nkStmtListExpr} and it.len > 0: + it = it.lastSon + result = it.kind == nkRaiseStmt or + it.kind in nkCallKinds and it[0].kind == nkSym and sfNoReturn in it[0].sym.flags + +proc commonType*(x: PType, y: PNode): PType = + # ignore exception raising branches in case/if expressions + if endsInNoReturn(y): return x + commonType(x, y.typ) + proc newSymS(kind: TSymKind, n: PNode, c: PContext): PSym = result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info) when defined(nimsuggest): @@ -423,7 +436,7 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, result = evalMacroCall(c.module, c.cache, n, nOrig, sym) if efNoSemCheck notin flags: result = semAfterMacroCall(c, n, result, sym, flags) - result = wrapInComesFrom(nOrig.info, result) + result = wrapInComesFrom(nOrig.info, sym, result) popInfoContext() proc forceBool(c: PContext, n: PNode): PNode = @@ -488,6 +501,8 @@ proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = proc myOpenCached(graph: ModuleGraph; module: PSym; rd: PRodReader): PPassContext = result = myOpen(graph, module, rd.cache) + +proc replayMethodDefs(graph: ModuleGraph; rd: PRodReader) = for m in items(rd.methods): methodDef(graph, m, true) proc isImportSystemStmt(n: PNode): bool = @@ -594,6 +609,8 @@ proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode = addCodeForGenerics(c, result) if c.module.ast != nil: result.add(c.module.ast) + if c.rd != nil: + replayMethodDefs(graph, c.rd) popOwner(c) popProcCon(c) if c.runnableExamples != nil: testExamples(c) diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim index db08605cfb..bbd2baf6e0 100644 --- a/compiler/semasgn.nim +++ b/compiler/semasgn.nim @@ -242,9 +242,9 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) = tyTypeDesc, tyGenericInvocation, tyForward: internalError(c.info, "assignment requested for type: " & typeToString(t)) of tyOrdinal, tyRange, tyInferred, - tyGenericInst, tyStatic, tyVar, tyAlias: + tyGenericInst, tyStatic, tyVar, tyLent, tyAlias, tySink: liftBodyAux(c, lastSon(t), body, x, y) - of tyUnused, tyOptAsRef, tyUnused1, tyUnused2: internalError("liftBodyAux") + of tyUnused, tyOptAsRef: internalError("liftBodyAux") proc newProcType(info: TLineInfo; owner: PSym): PType = result = newType(tyProc, owner) @@ -261,7 +261,7 @@ proc addParam(procType: PType; param: PSym) = rawAddSon(procType, param.typ) proc liftBody(c: PContext; typ: PType; kind: TTypeAttachedOp; - info: TLineInfo): PSym {.discardable.} = + info: TLineInfo): PSym = var a: TLiftCtx a.info = info a.c = c @@ -306,7 +306,7 @@ proc liftBody(c: PContext; typ: PType; kind: TTypeAttachedOp; proc getAsgnOrLiftBody(c: PContext; typ: PType; info: TLineInfo): PSym = - let t = typ.skipTypes({tyGenericInst, tyVar, tyAlias}) + let t = typ.skipTypes({tyGenericInst, tyVar, tyLent, tyAlias, tySink}) result = t.assignment if result.isNil: result = liftBody(c, t, attachedAsgn, info) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index a51b9afe30..c580f8fd54 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -377,7 +377,7 @@ proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode = proc canDeref(n: PNode): bool {.inline.} = result = n.len >= 2 and (let t = n[1].typ; - t != nil and t.skipTypes({tyGenericInst, tyAlias}).kind in {tyPtr, tyRef}) + t != nil and t.skipTypes({tyGenericInst, tyAlias, tySink}).kind in {tyPtr, tyRef}) proc tryDeref(n: PNode): PNode = result = newNodeI(nkHiddenDeref, n.info) diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 8affee649f..3996188dc2 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -259,19 +259,19 @@ proc makePtrType*(c: PContext, baseType: PType): PType = proc makeTypeWithModifier*(c: PContext, modifier: TTypeKind, baseType: PType): PType = - assert modifier in {tyVar, tyPtr, tyRef, tyStatic, tyTypeDesc} + assert modifier in {tyVar, tyLent, tyPtr, tyRef, tyStatic, tyTypeDesc} - if modifier in {tyVar, tyTypeDesc} and baseType.kind == modifier: + if modifier in {tyVar, tyLent, tyTypeDesc} and baseType.kind == modifier: result = baseType else: result = newTypeS(modifier, c) addSonSkipIntLit(result, baseType.assertNotNil) -proc makeVarType*(c: PContext, baseType: PType): PType = - if baseType.kind == tyVar: +proc makeVarType*(c: PContext, baseType: PType; kind = tyVar): PType = + if baseType.kind == kind: result = baseType else: - result = newTypeS(tyVar, c) + result = newTypeS(kind, c) addSonSkipIntLit(result, baseType.assertNotNil) proc makeTypeDesc*(c: PContext, typ: PType): PType = diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 65b111d8f5..e737f7676d 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -32,7 +32,7 @@ proc semOperand(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = # XXX tyGenericInst here? if result.typ.kind == tyProc and tfUnresolved in result.typ.flags: localError(n.info, errProcHasNoConcreteType, n.renderTree) - if result.typ.kind == tyVar: result = newDeref(result) + if result.typ.kind in {tyVar, tyLent}: result = newDeref(result) elif {efWantStmt, efAllowStmt} * flags != {}: result.typ = newTypeS(tyVoid, c) else: @@ -52,7 +52,7 @@ proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = result.typ = errorType(c) else: if efNoProcvarCheck notin flags: semProcvarCheck(c, result) - if result.typ.kind == tyVar: result = newDeref(result) + if result.typ.kind in {tyVar, tyLent}: result = newDeref(result) proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = result = semExpr(c, n, flags) @@ -181,9 +181,18 @@ proc semConv(c: PContext, n: PNode): PNode = result = newNodeI(nkConv, n.info) var targetType = semTypeNode(c, n.sons[0], nil).skipTypes({tyTypeDesc}) maybeLiftType(targetType, c, n[0].info) - result.addSon copyTree(n.sons[0]) - var op = semExprWithType(c, n.sons[1]) + if targetType.kind in {tySink, tyLent}: + let baseType = semTypeNode(c, n.sons[1], nil).skipTypes({tyTypeDesc}) + let t = newTypeS(targetType.kind, c) + t.rawAddSonNoPropagationOfTypeFlags baseType + result = newNodeI(nkType, n.info) + result.typ = makeTypeDesc(c, t) + return + + result.addSon copyTree(n.sons[0]) + + var op = semExprWithType(c, n.sons[1]) if targetType.isMetaType: let final = inferWithMetatype(c, targetType, op, true) result.addSon final @@ -191,6 +200,8 @@ proc semConv(c: PContext, n: PNode): PNode = return result.typ = targetType + # XXX op is overwritten later on, this is likely added too early + # here or needs to be overwritten too then. addSon(result, op) if not isSymChoice(op): @@ -350,7 +361,7 @@ proc changeType(n: PNode, newType: PType, check: bool) = for i in countup(0, sonsLen(n) - 1): changeType(n.sons[i], elemType(newType), check) of nkPar: - let tup = newType.skipTypes({tyGenericInst, tyAlias}) + let tup = newType.skipTypes({tyGenericInst, tyAlias, tySink}) if tup.kind != tyTuple: if tup.kind == tyObject: return globalError(n.info, "no tuple type for constructor") @@ -393,7 +404,7 @@ proc arrayConstrType(c: PContext, n: PNode): PType = if sonsLen(n) == 0: rawAddSon(typ, newTypeS(tyEmpty, c)) # needs an empty basetype! else: - var t = skipTypes(n.sons[0].typ, {tyGenericInst, tyVar, tyOrdinal, tyAlias}) + var t = skipTypes(n.sons[0].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) addSonSkipIntLit(typ, t) typ.sons[0] = makeRangeType(c, 0, sonsLen(n) - 1, n.info) result = typ @@ -417,7 +428,7 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = let yy = semExprWithType(c, x) var typ = yy.typ addSon(result, yy) - #var typ = skipTypes(result.sons[0].typ, {tyGenericInst, tyVar, tyOrdinal}) + #var typ = skipTypes(result.sons[0].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal}) for i in countup(1, sonsLen(n) - 1): x = n.sons[i] if x.kind == nkExprColonExpr and sonsLen(x) == 2: @@ -471,7 +482,7 @@ proc analyseIfAddressTaken(c: PContext, n: PNode): PNode = of nkSym: # n.sym.typ can be nil in 'check' mode ... if n.sym.typ != nil and - skipTypes(n.sym.typ, abstractInst-{tyTypeDesc}).kind != tyVar: + skipTypes(n.sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}: incl(n.sym.flags, sfAddrTaken) result = newHiddenAddrTaken(c, n) of nkDotExpr: @@ -479,12 +490,12 @@ proc analyseIfAddressTaken(c: PContext, n: PNode): PNode = if n.sons[1].kind != nkSym: internalError(n.info, "analyseIfAddressTaken") return - if skipTypes(n.sons[1].sym.typ, abstractInst-{tyTypeDesc}).kind != tyVar: + if skipTypes(n.sons[1].sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}: incl(n.sons[1].sym.flags, sfAddrTaken) result = newHiddenAddrTaken(c, n) of nkBracketExpr: checkMinSonsLen(n, 1) - if skipTypes(n.sons[0].typ, abstractInst-{tyTypeDesc}).kind != tyVar: + if skipTypes(n.sons[0].typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}: if n.sons[0].kind == nkSym: incl(n.sons[0].sym.flags, sfAddrTaken) result = newHiddenAddrTaken(c, n) else: @@ -499,7 +510,7 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) = # get the real type of the callee # it may be a proc var with a generic alias type, so we skip over them - var t = n.sons[0].typ.skipTypes({tyGenericInst, tyAlias}) + var t = n.sons[0].typ.skipTypes({tyGenericInst, tyAlias, tySink}) if n.sons[0].kind == nkSym and n.sons[0].sym.magic in FakeVarParams: # BUGFIX: check for L-Value still needs to be done for the arguments! @@ -692,7 +703,7 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = else: n.sons[0] = semExpr(c, n.sons[0], {efInCall}) let t = n.sons[0].typ - if t != nil and t.kind == tyVar: + if t != nil and t.kind in {tyVar, tyLent}: n.sons[0] = newDeref(n.sons[0]) elif n.sons[0].kind == nkBracketExpr: let s = bracketedMacro(n.sons[0]) @@ -780,6 +791,19 @@ proc buildEchoStmt(c: PContext, n: PNode): PNode = proc semExprNoType(c: PContext, n: PNode): PNode = result = semExpr(c, n, {efWantStmt}) + # make an 'if' expression an 'if' statement again for backwards + # compatibility (.discardable was a bad idea!); bug #6980 + var isStmt = false + if result.kind == nkIfExpr: + isStmt = true + for condActionPair in result: + let action = condActionPair.lastSon + if not implicitlyDiscardable(action) and not + endsInNoReturn(action): + isStmt = false + if isStmt: + result.kind = nkIfStmt + result.typ = nil discardCheck(c, result) proc isTypeExpr(n: PNode): bool = @@ -852,7 +876,7 @@ proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent, const tyTypeParamsHolders = {tyGenericInst, tyCompositeTypeClass} - tyDotOpTransparent = {tyVar, tyPtr, tyRef, tyAlias} + tyDotOpTransparent = {tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink} proc readTypeParameter(c: PContext, typ: PType, paramName: PIdent, info: TLineInfo): PNode = @@ -927,7 +951,8 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = else: result = semMacroExpr(c, n, n, s, flags) of skTemplate: - if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0: + if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or + sfCustomPragma in sym.flags: markUsed(n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(s, n.info) @@ -985,8 +1010,8 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = while p != nil and p.selfSym == nil: p = p.next if p != nil and p.selfSym != nil: - var ty = skipTypes(p.selfSym.typ, {tyGenericInst, tyVar, tyPtr, tyRef, - tyAlias}) + var ty = skipTypes(p.selfSym.typ, {tyGenericInst, tyVar, tyLent, tyPtr, tyRef, + tyAlias, tySink}) while tfBorrowDot in ty.flags: ty = ty.skipTypes({tyDistinct}) var check: PNode = nil if ty.kind == tyObject: @@ -1095,7 +1120,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = return nil if ty.kind in tyUserTypeClasses and ty.isResolvedUserTypeClass: ty = ty.lastSon - ty = skipTypes(ty, {tyGenericInst, tyVar, tyPtr, tyRef, tyAlias}) + ty = skipTypes(ty, {tyGenericInst, tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink}) while tfBorrowDot in ty.flags: ty = ty.skipTypes({tyDistinct}) var check: PNode = nil if ty.kind == tyObject: @@ -1162,7 +1187,7 @@ proc semDeref(c: PContext, n: PNode): PNode = checkSonsLen(n, 1) n.sons[0] = semExprWithType(c, n.sons[0]) result = n - var t = skipTypes(n.sons[0].typ, {tyGenericInst, tyVar, tyAlias}) + var t = skipTypes(n.sons[0].typ, {tyGenericInst, tyVar, tyLent, tyAlias, tySink}) case t.kind of tyRef, tyPtr: n.typ = t.lastSon else: result = nil @@ -1182,7 +1207,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = n.sons[0] = semExprWithType(c, n.sons[0], {efNoProcvarCheck, efNoEvaluateGeneric}) let arr = skipTypes(n.sons[0].typ, {tyGenericInst, - tyVar, tyPtr, tyRef, tyAlias}) + tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink}) case arr.kind of tyArray, tyOpenArray, tyVarargs, tySequence, tyString, tyCString: @@ -1210,7 +1235,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = n.sons[0] = makeDeref(n.sons[0]) # [] operator for tuples requires constant expression: n.sons[1] = semConstExpr(c, n.sons[1]) - if skipTypes(n.sons[1].typ, {tyGenericInst, tyRange, tyOrdinal, tyAlias}).kind in + if skipTypes(n.sons[1].typ, {tyGenericInst, tyRange, tyOrdinal, tyAlias, tySink}).kind in {tyInt..tyInt64}: var idx = getOrdValue(n.sons[1]) if idx >= 0 and idx < sonsLen(arr): n.typ = arr.sons[int(idx)] @@ -1290,13 +1315,10 @@ proc takeImplicitAddr(c: PContext, n: PNode): PNode = proc asgnToResultVar(c: PContext, n, le, ri: PNode) {.inline.} = if le.kind == nkHiddenDeref: var x = le.sons[0] - if x.typ.kind == tyVar and x.kind == nkSym: - if x.sym.kind == skResult: - n.sons[0] = x # 'result[]' --> 'result' - n.sons[1] = takeImplicitAddr(c, ri) - if x.sym.kind != skParam: - # XXX This is hacky. See bug #4910. - x.typ.flags.incl tfVarIsPtr + if x.typ.kind == tyVar and x.kind == nkSym and x.sym.kind == skResult: + n.sons[0] = x # 'result[]' --> 'result' + n.sons[1] = takeImplicitAddr(c, ri) + x.typ.flags.incl tfVarIsPtr #echo x.info, " setting it for this type ", typeToString(x.typ), " ", n.info template resultTypeIsInferrable(typ: PType): untyped = @@ -1352,7 +1374,7 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = # a = b # both are vars, means: a[] = b[] # a = b # b no 'var T' means: a = addr(b) var le = a.typ - if (skipTypes(le, {tyGenericInst, tyAlias}).kind != tyVar and + if (skipTypes(le, {tyGenericInst, tyAlias, tySink}).kind != tyVar and isAssignable(c, a) == arNone) or skipTypes(le, abstractVar).kind in {tyOpenArray, tyVarargs}: # Direct assignment to a discriminant is allowed! @@ -1368,13 +1390,16 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = if lhsIsResult: n.typ = enforceVoidContext if c.p.owner.kind != skMacro and resultTypeIsInferrable(lhs.sym.typ): - if cmpTypes(c, lhs.typ, rhs.typ) == isGeneric: + var rhsTyp = rhs.typ + if rhsTyp.kind in tyUserTypeClasses and rhsTyp.isResolvedUserTypeClass: + rhsTyp = rhsTyp.lastSon + if cmpTypes(c, lhs.typ, rhsTyp) in {isGeneric, isEqual}: internalAssert c.p.resultSym != nil - lhs.typ = rhs.typ - c.p.resultSym.typ = rhs.typ - c.p.owner.typ.sons[0] = rhs.typ + lhs.typ = rhsTyp + c.p.resultSym.typ = rhsTyp + c.p.owner.typ.sons[0] = rhsTyp else: - typeMismatch(n.info, lhs.typ, rhs.typ) + typeMismatch(n.info, lhs.typ, rhsTyp) n.sons[1] = fitNode(c, le, rhs, n.info) if not newDestructors: @@ -1412,11 +1437,7 @@ proc semProcBody(c: PContext, n: PNode): PNode = openScope(c) result = semExpr(c, n) if c.p.resultSym != nil and not isEmptyType(result.typ): - # transform ``expr`` to ``result = expr``, but not if the expr is already - # ``result``: - if result.kind == nkSym and result.sym == c.p.resultSym: - discard - elif result.kind == nkNilLit: + if result.kind == nkNilLit: # or ImplicitlyDiscardable(result): # new semantic: 'result = x' triggers the void context result.typ = nil @@ -1446,17 +1467,18 @@ proc semProcBody(c: PContext, n: PNode): PNode = closeScope(c) proc semYieldVarResult(c: PContext, n: PNode, restype: PType) = - var t = skipTypes(restype, {tyGenericInst, tyAlias}) + var t = skipTypes(restype, {tyGenericInst, tyAlias, tySink}) case t.kind - of tyVar: + of tyVar, tyLent: + if t.kind == tyVar: t.flags.incl tfVarIsPtr # bugfix for #4048, #4910, #6892 if n.sons[0].kind in {nkHiddenStdConv, nkHiddenSubConv}: n.sons[0] = n.sons[0].sons[1] - n.sons[0] = takeImplicitAddr(c, n.sons[0]) of tyTuple: for i in 0..= 2 and n.lastSon.kind == nkStmtList: - if n.sons[0].kind == nkIdent: - if sfMainModule in c.module.flags: - let inp = toFullPath(c.module.info) - if c.runnableExamples == nil: - c.runnableExamples = newTree(nkStmtList, - newTree(nkImportStmt, newStrNode(nkStrLit, expandFilename(inp)))) - c.runnableExamples.add newTree(nkBlockStmt, emptyNode, copyTree n.lastSon) - result = setMs(n, s) + if sfMainModule in c.module.flags: + let inp = toFullPath(c.module.info) + if c.runnableExamples == nil: + c.runnableExamples = newTree(nkStmtList, + newTree(nkImportStmt, newStrNode(nkStrLit, expandFilename(inp)))) + let imports = newTree(nkStmtList) + extractImports(n.lastSon, imports) + for imp in imports: c.runnableExamples.add imp + c.runnableExamples.add newTree(nkBlockStmt, emptyNode, copyTree n.lastSon) + result = setMs(n, s) else: result = emptyNode else: @@ -1936,17 +1967,17 @@ proc semSetConstr(c: PContext, n: PNode): PNode = n.sons[i].sons[2] = semExprWithType(c, n.sons[i].sons[2]) if typ == nil: typ = skipTypes(n.sons[i].sons[1].typ, - {tyGenericInst, tyVar, tyOrdinal, tyAlias}) + {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) n.sons[i].typ = n.sons[i].sons[2].typ # range node needs type too elif n.sons[i].kind == nkRange: # already semchecked if typ == nil: typ = skipTypes(n.sons[i].sons[0].typ, - {tyGenericInst, tyVar, tyOrdinal, tyAlias}) + {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) else: n.sons[i] = semExprWithType(c, n.sons[i]) if typ == nil: - typ = skipTypes(n.sons[i].typ, {tyGenericInst, tyVar, tyOrdinal, tyAlias}) + typ = skipTypes(n.sons[i].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) if not isOrdinalType(typ): localError(n.info, errOrdinalTypeExpected) typ = makeRangeType(c, 0, MaxSetElements-1, n.info) @@ -2123,6 +2154,8 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkIdent, nkAccQuoted: let checks = if efNoEvaluateGeneric in flags: {checkUndeclared, checkPureEnumFields} + elif efInCall in flags: + {checkUndeclared, checkModule, checkPureEnumFields} else: {checkUndeclared, checkModule, checkAmbiguity, checkPureEnumFields} var s = qualifiedLookUp(c, n, checks) @@ -2217,10 +2250,10 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = # XXX think about this more (``set`` procs) if n.len == 2: result = semConv(c, n) + elif contains(c.ambiguousSymbols, s.id) and n.len == 1: + errorUseQualifier(c, n.info, s) elif n.len == 1: result = semObjConstr(c, n, flags) - elif contains(c.ambiguousSymbols, s.id): - errorUseQualifier(c, n.info, s) elif s.magic == mNone: result = semDirectOp(c, n, flags) else: result = semMagic(c, n, s, flags) of skProc, skFunc, skMethod, skConverter, skIterator: @@ -2370,6 +2403,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = if n.len != 1 and n.len != 2: illFormedAst(n) for i in 0 ..< n.len: n.sons[i] = semExpr(c, n.sons[i]) + of nkComesFrom: discard "ignore the comes from information for now" else: localError(n.info, errInvalidExpressionX, renderTree(n, {renderNoComments})) diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 1e7c0aa9e4..f690615aca 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -408,7 +408,7 @@ proc getArrayConstr(m: PSym, n: PNode): PNode = proc foldArrayAccess(m: PSym, n: PNode): PNode = var x = getConstExpr(m, n.sons[0]) - if x == nil or x.typ.skipTypes({tyGenericInst, tyAlias}).kind == tyTypeDesc: + if x == nil or x.typ.skipTypes({tyGenericInst, tyAlias, tySink}).kind == tyTypeDesc: return var y = getConstExpr(m, n.sons[1]) @@ -655,5 +655,8 @@ proc getConstExpr(m: PSym, n: PNode): PNode = result.typ = n.typ of nkBracketExpr: result = foldArrayAccess(m, n) of nkDotExpr: result = foldFieldAccess(m, n) + of nkStmtListExpr: + if n.len == 2 and n[0].kind == nkComesFrom: + result = getConstExpr(m, n[1]) else: discard diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index da2c6fe7f3..16da06952a 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -186,7 +186,7 @@ proc semGenericStmt(c: PContext, n: PNode, let a = n.sym let b = getGenSym(c, a) if b != a: n.sym = b - of nkEmpty, succ(nkSym)..nkNilLit: + of nkEmpty, succ(nkSym)..nkNilLit, nkComesFrom: # see tests/compile/tgensymgeneric.nim: # We need to open the gensym'ed symbol again so that the instantiation # creates a fresh copy; but this is wrong the very first reason for gensym diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 0d0f2ee82e..189babdecf 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -108,7 +108,7 @@ proc uninstantiate(t: PType): PType = else: t proc evalTypeTrait(traitCall: PNode, operand: PType, context: PSym): PNode = - const skippedTypes = {tyTypeDesc, tyAlias} + const skippedTypes = {tyTypeDesc, tyAlias, tySink} let trait = traitCall[0] internalAssert trait.kind == nkSym var operand = operand.skipTypes(skippedTypes) @@ -145,7 +145,7 @@ proc evalTypeTrait(traitCall: PNode, operand: PType, context: PSym): PNode = of "stripGenericParams": result = uninstantiate(operand).toNode(traitCall.info) of "supportsCopyMem": - let t = operand.skipTypes({tyVar, tyGenericInst, tyAlias, tyInferred}) + let t = operand.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink, tyInferred}) let complexObj = containsGarbageCollectedRef(t) or hasDestructor(t) result = newIntNodeT(ord(not complexObj), traitCall) diff --git a/compiler/semobjconstr.nim b/compiler/semobjconstr.nim index 56d160aa42..46e6ef25d4 100644 --- a/compiler/semobjconstr.nim +++ b/compiler/semobjconstr.nim @@ -39,13 +39,19 @@ proc mergeInitStatus(existing: var InitStatus, newStatus: InitStatus) = of initUnknown: discard +proc invalidObjConstr(n: PNode) = + if n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.s[0] == ':': + localError(n.info, "incorrect object construction syntax; use a space after the colon") + else: + localError(n.info, "incorrect object construction syntax") + proc locateFieldInInitExpr(field: PSym, initExpr: PNode): PNode = # Returns the assignment nkExprColonExpr node or nil let fieldId = field.name.id for i in 1 ..< initExpr.len: let assignment = initExpr[i] if assignment.kind != nkExprColonExpr: - localError(initExpr.info, "incorrect object construction syntax") + invalidObjConstr(assignment) continue if fieldId == considerQuotedIdent(assignment[0]).id: @@ -254,8 +260,8 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = result = newNodeIT(nkObjConstr, n.info, t) for child in n: result.add child - t = skipTypes(t, {tyGenericInst, tyAlias}) - if t.kind == tyRef: t = skipTypes(t.sons[0], {tyGenericInst, tyAlias}) + t = skipTypes(t, {tyGenericInst, tyAlias, tySink}) + if t.kind == tyRef: t = skipTypes(t.sons[0], {tyGenericInst, tyAlias, tySink}) if t.kind != tyObject: localError(n.info, errGenerated, "object constructor needs an object type") return @@ -284,7 +290,7 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = let field = result[i] if nfSem notin field.flags: if field.kind != nkExprColonExpr: - localError(n.info, "incorrect object construction syntax") + invalidObjConstr(field) continue let id = considerQuotedIdent(field[0]) # This node was not processed. There are two possible reasons: diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index d1f8d89c99..d427750e40 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -979,10 +979,10 @@ proc trackProc*(s: PSym, body: PNode) = message(s.info, warnLockLevel, "declared lock level is $1, but real lock level is $2" % [$s.typ.lockLevel, $t.maxLockLevel]) - when false: + when defined(useDfa): if s.kind == skFunc: - when defined(dfa): dataflowAnalysis(s, body) - trackWrites(s, body) + dataflowAnalysis(s, body) + when false: trackWrites(s, body) proc trackTopLevelStmt*(module: PSym; n: PNode) = if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkFuncDef, diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index e01f867faf..ccddabcbec 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -102,7 +102,7 @@ proc semExprBranch(c: PContext, n: PNode): PNode = if result.typ != nil: # XXX tyGenericInst here? semProcvarCheck(c, result) - if result.typ.kind == tyVar: result = newDeref(result) + if result.typ.kind in {tyVar, tyLent}: result = newDeref(result) proc semExprBranchScope(c: PContext, n: PNode): PNode = openScope(c) @@ -165,14 +165,14 @@ proc semIf(c: PContext, n: PNode): PNode = it.sons[0] = forceBool(c, semExprWithType(c, it.sons[0])) when not newScopeForIf: openScope(c) it.sons[1] = semExprBranch(c, it.sons[1]) - typ = commonType(typ, it.sons[1].typ) + typ = commonType(typ, it.sons[1]) closeScope(c) elif it.len == 1: hasElse = true it.sons[0] = semExprBranchScope(c, it.sons[0]) - typ = commonType(typ, it.sons[0].typ) + typ = commonType(typ, it.sons[0]) else: illFormedAst(it) - if isEmptyType(typ) or typ.kind == tyNil or not hasElse: + if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse: for it in n: discardCheck(c, it.lastSon) result.kind = nkIfStmt # propagate any enforced VoidContext: @@ -180,7 +180,8 @@ proc semIf(c: PContext, n: PNode): PNode = else: for it in n: let j = it.len-1 - it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) + if not endsInNoReturn(it.sons[j]): + it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) result.kind = nkIfExpr result.typ = typ @@ -213,7 +214,7 @@ proc semCase(c: PContext, n: PNode): PNode = semCaseBranch(c, n, x, i, covered) var last = sonsLen(x)-1 x.sons[last] = semExprBranchScope(c, x.sons[last]) - typ = commonType(typ, x.sons[last].typ) + typ = commonType(typ, x.sons[last]) of nkElifBranch: chckCovered = false checkSonsLen(x, 2) @@ -221,13 +222,13 @@ proc semCase(c: PContext, n: PNode): PNode = x.sons[0] = forceBool(c, semExprWithType(c, x.sons[0])) when not newScopeForIf: openScope(c) x.sons[1] = semExprBranch(c, x.sons[1]) - typ = commonType(typ, x.sons[1].typ) + typ = commonType(typ, x.sons[1]) closeScope(c) of nkElse: chckCovered = false checkSonsLen(x, 1) x.sons[0] = semExprBranchScope(c, x.sons[0]) - typ = commonType(typ, x.sons[0].typ) + typ = commonType(typ, x.sons[0]) hasElse = true else: illFormedAst(x) @@ -237,7 +238,7 @@ proc semCase(c: PContext, n: PNode): PNode = else: localError(n.info, errNotAllCasesCovered) closeScope(c) - if isEmptyType(typ) or typ.kind == tyNil or not hasElse: + if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse: for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon) # propagate any enforced VoidContext: if typ == enforceVoidContext: @@ -246,7 +247,8 @@ proc semCase(c: PContext, n: PNode): PNode = for i in 1..n.len-1: var it = n.sons[i] let j = it.len-1 - it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) + if not endsInNoReturn(it.sons[j]): + it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) result.typ = typ proc semTry(c: PContext, n: PNode): PNode = @@ -441,20 +443,21 @@ proc hasEmpty(typ: PType): bool = result = result or hasEmpty(s) proc makeDeref(n: PNode): PNode = - var t = skipTypes(n.typ, {tyGenericInst, tyAlias}) + var t = n.typ if t.kind in tyUserTypeClasses and t.isResolvedUserTypeClass: t = t.lastSon + t = skipTypes(t, {tyGenericInst, tyAlias, tySink}) result = n - if t.kind == tyVar: + if t.kind in {tyVar, tyLent}: result = newNodeIT(nkHiddenDeref, n.info, t.sons[0]) addSon(result, n) - t = skipTypes(t.sons[0], {tyGenericInst, tyAlias}) + t = skipTypes(t.sons[0], {tyGenericInst, tyAlias, tySink}) while t.kind in {tyPtr, tyRef}: var a = result let baseTyp = t.lastSon result = newNodeIT(nkHiddenDeref, n.info, baseTyp) addSon(result, a) - t = skipTypes(baseTyp, {tyGenericInst, tyAlias}) + t = skipTypes(baseTyp, {tyGenericInst, tyAlias, tySink}) proc fillPartialObject(c: PContext; n: PNode; typ: PType) = if n.len == 2: @@ -530,7 +533,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = if typ == nil: continue typeAllowedCheck(a.info, typ, symkind) liftTypeBoundOps(c, typ, a.info) - var tup = skipTypes(typ, {tyGenericInst, tyAlias}) + var tup = skipTypes(typ, {tyGenericInst, tyAlias, tySink}) if a.kind == nkVarTuple: if tup.kind != tyTuple: localError(a.info, errXExpected, "tuple") @@ -646,7 +649,7 @@ proc semForVars(c: PContext, n: PNode): PNode = result = n var length = sonsLen(n) let iterBase = n.sons[length-2].typ - var iter = skipTypes(iterBase, {tyGenericInst, tyAlias}) + var iter = skipTypes(iterBase, {tyGenericInst, tyAlias, tySink}) # length == 3 means that there is one for loop variable # and thus no tuple unpacking: if iter.kind != tyTuple or length == 3: @@ -680,7 +683,7 @@ proc semForVars(c: PContext, n: PNode): PNode = proc implicitIterator(c: PContext, it: string, arg: PNode): PNode = result = newNodeI(nkCall, arg.info) result.add(newIdentNode(it.getIdent, arg.info)) - if arg.typ != nil and arg.typ.kind == tyVar: + if arg.typ != nil and arg.typ.kind in {tyVar, tyLent}: result.add newDeref(arg) else: result.add arg @@ -730,6 +733,18 @@ proc semRaise(c: PContext, n: PNode): PNode = if typ.kind != tyRef or typ.lastSon.kind != tyObject: localError(n.info, errExprCannotBeRaised) + # check if the given object inherits from Exception + var base = typ.lastSon + while true: + if base.sym.magic == mException: + break + if base.lastSon == nil: + localError(n.info, + "raised object of type $1 does not inherit from Exception", + [typeToString(typ)]) + return + base = base.lastSon + proc addGenericParamListToScope(c: PContext, n: PNode) = if n.kind != nkGenericParams: illFormedAst(n) for i in countup(0, sonsLen(n)-1): @@ -865,11 +880,11 @@ proc checkCovariantParamsUsages(genericType: PType) = for fieldType in t.sons: subresult traverseSubTypes(fieldType) - of tyPtr, tyRef, tyVar: + of tyPtr, tyRef, tyVar, tyLent: if t.base.kind == tyGenericParam: return true return traverseSubTypes(t.base) - of tyDistinct, tyAlias: + of tyDistinct, tyAlias, tySink: return traverseSubTypes(t.lastSon) of tyGenericInst: @@ -979,8 +994,8 @@ proc checkForMetaFields(n: PNode) = of nkSym: let t = n.sym.typ case t.kind - of tySequence, tySet, tyArray, tyOpenArray, tyVar, tyPtr, tyRef, - tyProc, tyGenericInvocation, tyGenericInst, tyAlias: + of tySequence, tySet, tyArray, tyOpenArray, tyVar, tyLent, tyPtr, tyRef, + tyProc, tyGenericInvocation, tyGenericInst, tyAlias, tySink: let start = int ord(t.kind in {tyGenericInvocation, tyGenericInst}) for i in start ..< t.sons.len: checkMeta(t.sons[i]) @@ -1005,7 +1020,7 @@ proc typeSectionFinalPass(c: PContext, n: PNode) = # type aliases are hard: var t = semTypeNode(c, x, nil) assert t != nil - if s.typ != nil and s.typ.kind != tyAlias: + if s.typ != nil and s.typ.kind notin {tyAlias, tySink}: if t.kind in {tyProc, tyGenericInst} and not t.isMetaType: assignType(s.typ, t) s.typ.id = t.id @@ -1138,6 +1153,9 @@ proc semProcAnnotation(c: PContext, prc: PNode; else: localError(prc.info, errOnlyACallOpCanBeDelegator) continue + elif sfCustomPragma in m.flags: + continue # semantic check for custom pragma happens later in semProcAux + # we transform ``proc p {.m, rest.}`` into ``m(do: proc p {.rest.})`` and # let the semantic checker deal with it: var x = newNodeI(nkCall, n.info) @@ -1400,9 +1418,9 @@ proc semMethodPrototype(c: PContext; s: PSym; n: PNode) = for col in countup(1, sonsLen(tt)-1): let t = tt.sons[col] if t != nil and t.kind == tyGenericInvocation: - var x = skipTypes(t.sons[0], {tyVar, tyPtr, tyRef, tyGenericInst, + var x = skipTypes(t.sons[0], {tyVar, tyLent, tyPtr, tyRef, tyGenericInst, tyGenericInvocation, tyGenericBody, - tyAlias}) + tyAlias, tySink}) if x.kind == tyObject and t.len-1 == n.sons[genericParamsPos].len: foundObj = true x.methods.safeAdd((col,s)) @@ -1840,8 +1858,8 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = else: n.typ = n.sons[i].typ if not isEmptyType(n.typ): n.kind = nkStmtListExpr - case n.sons[i].kind - of LastBlockStmts: + if n.sons[i].kind in LastBlockStmts or + n.sons[i].kind in nkCallKinds and n.sons[i][0].kind == nkSym and sfNoReturn in n.sons[i][0].sym.flags: for j in countup(i + 1, length - 1): case n.sons[j].kind of nkPragma, nkCommentStmt, nkNilLit, nkEmpty, nkBlockExpr, diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 1c9d8271af..454dadec09 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -331,7 +331,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = of nkMixinStmt: if c.scopeN > 0: result = semTemplBodySons(c, n) else: result = semMixinStmt(c.c, n, c.toMixin) - of nkEmpty, nkSym..nkNilLit: + of nkEmpty, nkSym..nkNilLit, nkComesFrom: discard of nkIfStmt: for i in countup(0, sonsLen(n)-1): @@ -528,7 +528,7 @@ proc semTemplBodyDirty(c: var TemplCtx, n: PNode): PNode = result = semTemplBodyDirty(c, n.sons[0]) of nkBindStmt: result = semBindStmt(c.c, n, c.toBind) - of nkEmpty, nkSym..nkNilLit: + of nkEmpty, nkSym..nkNilLit, nkComesFrom: discard else: # dotExpr is ambiguous: note that we explicitly allow 'x.TemplateParam', @@ -608,7 +608,10 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = popOwner(c) s.ast = n result = n - if n.sons[bodyPos].kind == nkEmpty: + if sfCustomPragma in s.flags: + if n.sons[bodyPos].kind != nkEmpty: + localError(n.sons[bodyPos].info, errImplOfXNotAllowed, s.name.s) + elif n.sons[bodyPos].kind == nkEmpty: localError(n.info, errImplOfXexpected, s.name.s) var proto = searchForProc(c, c.currentScope, s) if proto == nil: diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index b633c87ab8..5172bc6535 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -101,7 +101,7 @@ proc semSet(c: PContext, n: PNode, prev: PType): PType = if sonsLen(n) == 2: var base = semTypeNode(c, n.sons[1], nil) addSonSkipIntLit(result, base) - if base.kind in {tyGenericInst, tyAlias}: base = lastSon(base) + if base.kind in {tyGenericInst, tyAlias, tySink}: base = lastSon(base) if base.kind != tyGenericParam: if not isOrdinalType(base): localError(n.info, errOrdinalTypeExpected) @@ -153,7 +153,7 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType = isNilable = true else: let region = semTypeNode(c, ni, nil) - if region.skipTypes({tyGenericInst, tyAlias}).kind notin { + if region.skipTypes({tyGenericInst, tyAlias, tySink}).kind notin { tyError, tyObject}: message n[i].info, errGenerated, "region needs to be an object type" addSonSkipIntLit(result, region) @@ -286,7 +286,7 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType = # 3 = length(array indx base) let indx = semArrayIndex(c, n[1]) var indxB = indx - if indxB.kind in {tyGenericInst, tyAlias}: indxB = lastSon(indxB) + if indxB.kind in {tyGenericInst, tyAlias, tySink}: indxB = lastSon(indxB) if indxB.kind notin {tyGenericParam, tyStatic, tyFromExpr}: if not isOrdinalType(indxB): localError(n.sons[1].info, errOrdinalTypeExpected) @@ -320,11 +320,8 @@ proc semTypeIdent(c: PContext, n: PNode): PSym = if n.kind == nkSym: result = getGenSym(c, n.sym) else: - when defined(nimfix): - result = pickSym(c, n, skType) - if result.isNil: - result = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared}) - else: + result = pickSym(c, n, {skType, skGenericParam}) + if result.isNil: result = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared}) if result != nil: markUsed(n.info, result, c.graph.usageSym) @@ -676,7 +673,7 @@ proc skipGenericInvocation(t: PType): PType {.inline.} = result = t if result.kind == tyGenericInvocation: result = result.sons[0] - while result.kind in {tyGenericInst, tyGenericBody, tyRef, tyPtr, tyAlias}: + while result.kind in {tyGenericInst, tyGenericBody, tyRef, tyPtr, tyAlias, tySink}: result = lastSon(result) proc addInheritedFields(c: PContext, check: var IntSet, pos: var int, @@ -839,7 +836,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, result = liftingWalk(paramType.sons[0], true) of tySequence, tySet, tyArray, tyOpenArray, - tyVar, tyPtr, tyRef, tyProc: + tyVar, tyLent, tyPtr, tyRef, tyProc: # XXX: this is a bit strange, but proc(s: seq) # produces tySequence(tyGenericParam, tyNone). # This also seems to be true when creating aliases @@ -995,7 +992,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, if isType: localError(a.info, "':' expected") if kind in {skTemplate, skMacro}: typ = newTypeS(tyExpr, c) - elif skipTypes(typ, {tyGenericInst, tyAlias}).kind == tyVoid: + elif skipTypes(typ, {tyGenericInst, tyAlias, tySink}).kind == tyVoid: continue for j in countup(0, length-3): var arg = newSymG(skParam, a.sons[j], c) @@ -1027,7 +1024,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, if r != nil: # turn explicit 'void' return type into 'nil' because the rest of the # compiler only checks for 'nil': - if skipTypes(r, {tyGenericInst, tyAlias}).kind != tyVoid: + if skipTypes(r, {tyGenericInst, tyAlias, tySink}).kind != tyVoid: # 'auto' as a return type does not imply a generic: if r.kind == tyAnything: # 'p(): auto' and 'p(): expr' are equivalent, but the rest of the @@ -1338,7 +1335,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = semRangeAux(c, n, prev) elif n[0].kind == nkNilLit and n.len == 2: result = semTypeNode(c, n.sons[1], prev) - if result.skipTypes({tyGenericInst, tyAlias}).kind in NilableTypes+GenericTypes: + if result.skipTypes({tyGenericInst, tyAlias, tySink}).kind in NilableTypes+GenericTypes: if tfNotNil in result.flags: result = freshType(result, prev) result.flags.excl(tfNotNil) @@ -1366,7 +1363,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = case n.len of 3: result = semTypeNode(c, n.sons[1], prev) - if result.skipTypes({tyGenericInst, tyAlias}).kind in NilableTypes+GenericTypes+{tyForward} and + if result.skipTypes({tyGenericInst, tyAlias, tySink}).kind in NilableTypes+GenericTypes+{tyForward} and n.sons[2].kind == nkNilLit: result = freshType(result, prev) result.flags.incl(tfNotNil) @@ -1419,7 +1416,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of mVar: result = newOrPrevType(tyVar, prev, c) var base = semTypeNode(c, n.sons[1], nil) - if base.kind == tyVar: + if base.kind in {tyVar, tyLent}: localError(n.info, errVarVarTypeNotAllowed) base = base.sons[0] addSonSkipIntLit(result, base) @@ -1620,6 +1617,12 @@ proc processMagicType(c: PContext, m: PSym) = rawAddSon(m.typ, newTypeS(tyNone, c)) of mPNimrodNode: incl m.typ.flags, tfTriggersCompileTime + of mException: discard + of mBuiltinType: + case m.name.s + of "lent": setMagicType(m, tyLent, ptrSize) + of "sink": setMagicType(m, tySink, 0) + else: localError(m.info, errTypeExpected) else: localError(m.info, errTypeExpected) proc semGenericConstraints(c: PContext, x: PType): PType = diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index a42092ae0c..d9b368b0e7 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -17,7 +17,7 @@ const proc checkPartialConstructedType(info: TLineInfo, t: PType) = if tfAcyclic in t.flags and skipTypes(t, abstractInst).kind != tyObject: localError(info, errInvalidPragmaX, "acyclic") - elif t.kind == tyVar and t.sons[0].kind == tyVar: + elif t.kind in {tyVar, tyLent} and t.sons[0].kind in {tyVar, tyLent}: localError(info, errVarVarTypeNotAllowed) proc checkConstructedType*(info: TLineInfo, typ: PType) = @@ -25,7 +25,7 @@ proc checkConstructedType*(info: TLineInfo, typ: PType) = if t.kind in tyTypeClasses: discard elif tfAcyclic in t.flags and skipTypes(t, abstractInst).kind != tyObject: localError(info, errInvalidPragmaX, "acyclic") - elif t.kind == tyVar and t.sons[0].kind == tyVar: + elif t.kind in {tyVar, tyLent} and t.sons[0].kind in {tyVar, tyLent}: localError(info, errVarVarTypeNotAllowed) elif computeSize(t) == szIllegalRecursion: localError(info, errIllegalRecursionInTypeX, typeToString(t)) @@ -518,7 +518,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = var r = replaceTypeVarsT(cl, result.sons[i]) if result.kind == tyObject: # carefully coded to not skip the precious tyGenericInst: - let r2 = r.skipTypes({tyAlias}) + let r2 = r.skipTypes({tyAlias, tySink}) if r2.kind in {tyPtr, tyRef}: r = skipTypes(r2, {tyPtr, tyRef}) result.sons[i] = r diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 3d0b0ed3d6..fffe92b2fa 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -180,7 +180,8 @@ proc sumGeneric(t: PType): int = while true: case t.kind of tyGenericInst, tyArray, tyRef, tyPtr, tyDistinct, - tyOpenArray, tyVarargs, tySet, tyRange, tySequence, tyGenericBody: + tyOpenArray, tyVarargs, tySet, tyRange, tySequence, tyGenericBody, + tyLent: t = t.lastSon inc result of tyOr: @@ -207,7 +208,7 @@ proc sumGeneric(t: PType): int = of tyStatic: return t.sons[0].sumGeneric + 1 of tyGenericParam, tyExpr, tyStmt: break - of tyAlias: t = t.lastSon + of tyAlias, tySink: t = t.lastSon of tyBool, tyChar, tyEnum, tyObject, tyPointer, tyString, tyCString, tyInt..tyInt64, tyFloat..tyFloat128, tyUInt..tyUInt64, tyCompositeTypeClass: @@ -464,7 +465,7 @@ proc skipToObject(t: PType; skipped: var SkippedPtr): PType = inc ptrs skipped = skippedPtr r = r.lastSon - of tyGenericBody, tyGenericInst, tyAlias: + of tyGenericBody, tyGenericInst, tyAlias, tySink: r = r.lastSon else: break @@ -524,7 +525,7 @@ proc allowsNil(f: PType): TTypeRelation {.inline.} = result = if tfNotNil notin f.flags: isSubtype else: isNone proc inconsistentVarTypes(f, a: PType): bool {.inline.} = - result = f.kind != a.kind and (f.kind == tyVar or a.kind == tyVar) + result = f.kind != a.kind and (f.kind in {tyVar, tyLent} or a.kind in {tyVar, tyLent}) proc procParamTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = ## For example we have: @@ -889,7 +890,7 @@ proc inferStaticsInRange(c: var TCandidate, doInferStatic(lowerBound, upperBound.intVal + 1 - lengthOrd(concrete)) template subtypeCheck() = - if result <= isSubrange and f.lastSon.skipTypes(abstractInst).kind in {tyRef, tyPtr, tyVar}: + if result <= isSubrange and f.lastSon.skipTypes(abstractInst).kind in {tyRef, tyPtr, tyVar, tyLent}: result = isNone proc isCovariantPtr(c: var TCandidate, f, a: PType): bool = @@ -897,7 +898,7 @@ proc isCovariantPtr(c: var TCandidate, f, a: PType): bool = assert f.kind == a.kind template baseTypesCheck(lhs, rhs: PType): bool = - lhs.kind notin {tyPtr, tyRef, tyVar} and + lhs.kind notin {tyPtr, tyRef, tyVar, tyLent} and typeRel(c, lhs, rhs, {trNoCovariance}) == isSubtype case f.kind @@ -983,17 +984,17 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, template doBind: bool = trDontBind notin flags # var and static arguments match regular modifier-free types - var a = aOrig.skipTypes({tyStatic, tyVar}).maybeSkipDistinct(c.calleeSym) + var a = aOrig.skipTypes({tyStatic, tyVar, tyLent}).maybeSkipDistinct(c.calleeSym) # XXX: Theoretically, maybeSkipDistinct could be called before we even # start the param matching process. This could be done in `prepareOperand` # for example, but unfortunately `prepareOperand` is not called in certain # situation when nkDotExpr are rotated to nkDotCalls - if aOrig.kind == tyAlias: + if aOrig.kind in {tyAlias, tySink}: return typeRel(c, f, lastSon(aOrig)) if a.kind == tyGenericInst and - skipTypes(f, {tyVar}).kind notin { + skipTypes(f, {tyVar, tyLent}).kind notin { tyGenericBody, tyGenericInvocation, tyGenericInst, tyGenericParam} + tyTypeClasses: return typeRel(c, f, lastSon(a)) @@ -1105,8 +1106,8 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, of tyFloat32: result = handleFloatRange(f, a) of tyFloat64: result = handleFloatRange(f, a) of tyFloat128: result = handleFloatRange(f, a) - of tyVar: - if aOrig.kind == tyVar: result = typeRel(c, f.base, aOrig.base) + of tyVar, tyLent: + if aOrig.kind == f.kind: result = typeRel(c, f.base, aOrig.base) else: result = typeRel(c, f.base, aOrig, flags + {trNoCovariance}) subtypeCheck() of tyArray: @@ -1311,7 +1312,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, of tyEmpty, tyVoid: if a.kind == f.kind: result = isEqual - of tyAlias: + of tyAlias, tySink: result = typeRel(c, lastSon(f), a) of tyGenericInst: @@ -1497,7 +1498,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, considerPreviousT: let targetKind = f.sons[0].kind let effectiveArgType = a.skipTypes({tyRange, tyGenericInst, - tyBuiltInTypeClass, tyAlias}) + tyBuiltInTypeClass, tyAlias, tySink}) let typeClassMatches = targetKind == effectiveArgType.kind and not effectiveArgType.isEmptyContainer if typeClassMatches or @@ -2068,7 +2069,8 @@ proc prepareNamedParam(a: PNode) = proc arrayConstr(c: PContext, n: PNode): PType = result = newTypeS(tyArray, c) rawAddSon(result, makeRangeType(c, 0, 0, n.info)) - addSonSkipIntLit(result, skipTypes(n.typ, {tyGenericInst, tyVar, tyOrdinal})) + addSonSkipIntLit(result, skipTypes(n.typ, + {tyGenericInst, tyVar, tyLent, tyOrdinal})) proc arrayConstr(c: PContext, info: TLineInfo): PType = result = newTypeS(tyArray, c) diff --git a/compiler/suggest.nim b/compiler/suggest.nim index f9210cc93e..28ce3c591c 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -240,7 +240,7 @@ proc suggestField(c: PContext, s: PSym; f: PNode; info: TLineInfo; outputs: var proc getQuality(s: PSym): range[0..100] = if s.typ != nil and s.typ.len > 1: - var exp = s.typ.sons[1].skipTypes({tyGenericInst, tyVar, tyAlias}) + var exp = s.typ.sons[1].skipTypes({tyGenericInst, tyVar, tyLent, tyAlias, tySink}) if exp.kind == tyVarargs: exp = elemType(exp) if exp.kind in {tyExpr, tyStmt, tyGenericParam, tyAnything}: return 50 return 100 @@ -309,7 +309,7 @@ proc typeFits(c: PContext, s: PSym, firstArg: PType): bool {.inline.} = let m = s.getModule() if m != nil and sfSystemModule in m.flags: if s.kind == skType: return - var exp = s.typ.sons[1].skipTypes({tyGenericInst, tyVar, tyAlias}) + var exp = s.typ.sons[1].skipTypes({tyGenericInst, tyVar, tyLent, tyAlias, tySink}) if exp.kind == tyVarargs: exp = elemType(exp) if exp.kind in {tyExpr, tyStmt, tyGenericParam, tyAnything}: return result = sigmatch.argtypeMatches(c, s.typ.sons[1], firstArg) @@ -378,8 +378,8 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) t = t.sons[0] suggestOperations(c, n, field, typ, outputs) else: - let orig = typ # skipTypes(typ, {tyGenericInst, tyAlias}) - typ = skipTypes(typ, {tyGenericInst, tyVar, tyPtr, tyRef, tyAlias}) + let orig = typ # skipTypes(typ, {tyGenericInst, tyAlias, tySink}) + typ = skipTypes(typ, {tyGenericInst, tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink}) if typ.kind == tyObject: var t = typ while true: diff --git a/compiler/transf.nim b/compiler/transf.nim index 8e4bb935b0..14ff58c905 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -93,7 +93,7 @@ proc getCurrOwner(c: PTransf): PSym = proc newTemp(c: PTransf, typ: PType, info: TLineInfo): PNode = let r = newSym(skTemp, getIdent(genPrefix), getCurrOwner(c), info) - r.typ = typ #skipTypes(typ, {tyGenericInst, tyAlias}) + r.typ = typ #skipTypes(typ, {tyGenericInst, tyAlias, tySink}) incl(r.flags, sfFromGeneric) let owner = getCurrOwner(c) if owner.isIterator and not c.tooEarly: @@ -331,7 +331,7 @@ proc transformYield(c: PTransf, n: PNode): PTransNode = # c.transCon.forStmt.len == 3 means that there is one for loop variable # and thus no tuple unpacking: if e.typ.isNil: return result # can happen in nimsuggest for unknown reasons - if skipTypes(e.typ, {tyGenericInst, tyAlias}).kind == tyTuple and + if skipTypes(e.typ, {tyGenericInst, tyAlias, tySink}).kind == tyTuple and c.transCon.forStmt.len != 3: e = skipConv(e) if e.kind == nkPar: @@ -506,7 +506,7 @@ proc putArgInto(arg: PNode, formal: PType): TPutArgInto = if putArgInto(arg.sons[i], formal) != paDirectMapping: return result = paDirectMapping else: - if skipTypes(formal, abstractInst).kind == tyVar: result = paVarAsgn + if skipTypes(formal, abstractInst).kind in {tyVar, tyLent}: result = paVarAsgn else: result = paFastAsgn proc findWrongOwners(c: PTransf, n: PNode) = @@ -791,7 +791,7 @@ proc transform(c: PTransf, n: PNode): PTransNode = case n.kind of nkSym: result = transformSym(c, n) - of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: + of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, nkComesFrom: # nothing to be done for leaves: result = PTransNode(n) of nkBracketExpr: result = transformArrayAccess(c, n) @@ -914,7 +914,7 @@ proc processTransf(c: PTransf, n: PNode, owner: PSym): PNode = # Note: For interactive mode we cannot call 'passes.skipCodegen' and skip # this step! We have to rely that the semantic pass transforms too errornous # nodes into an empty node. - if c.fromCache or nfTransf in n.flags: return n + if c.rd != nil or nfTransf in n.flags: return n pushTransCon(c, newTransCon(owner)) result = PNode(transform(c, n)) popTransCon(c) diff --git a/compiler/trees.nim b/compiler/trees.nim index 7efefdc2ed..577ea75eeb 100644 --- a/compiler/trees.nim +++ b/compiler/trees.nim @@ -102,7 +102,7 @@ proc isDeepConstExpr*(n: PNode): bool = if not isDeepConstExpr(n.sons[i]): return false if n.typ.isNil: result = true else: - let t = n.typ.skipTypes({tyGenericInst, tyDistinct, tyAlias}) + let t = n.typ.skipTypes({tyGenericInst, tyDistinct, tyAlias, tySink}) if t.kind in {tyRef, tyPtr}: return false if t.kind != tyObject or not isCaseObj(t.n): result = true diff --git a/compiler/types.nim b/compiler/types.nim index 495a1977d8..cbbfa86317 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -51,17 +51,17 @@ const # TODO: Remove tyTypeDesc from each abstractX and (where necessary) # replace with typedescX abstractPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyDistinct, tyOrdinal, - tyTypeDesc, tyAlias, tyInferred} + tyTypeDesc, tyAlias, tyInferred, tySink, tyLent} abstractVar* = {tyVar, tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, - tyAlias, tyInferred} + tyAlias, tyInferred, tySink, tyLent} abstractRange* = {tyGenericInst, tyRange, tyDistinct, tyOrdinal, tyTypeDesc, - tyAlias, tyInferred} + tyAlias, tyInferred, tySink} abstractVarRange* = {tyGenericInst, tyRange, tyVar, tyDistinct, tyOrdinal, - tyTypeDesc, tyAlias, tyInferred} + tyTypeDesc, tyAlias, tyInferred, tySink} abstractInst* = {tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, - tyInferred} + tyInferred, tySink} skipPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyTypeDesc, tyAlias, - tyInferred} + tyInferred, tySink, tyLent} # typedescX is used if we're sure tyTypeDesc should be included (or skipped) typedescPtrs* = abstractPtrs + {tyTypeDesc} typedescInst* = abstractInst + {tyTypeDesc} @@ -388,8 +388,8 @@ const "int", "int8", "int16", "int32", "int64", "float", "float32", "float64", "float128", "uint", "uint8", "uint16", "uint32", "uint64", - "unused0", "unused1", - "unused2", "varargs[$1]", "unused", "Error Type", + "opt", "sink", + "lent", "varargs[$1]", "unused", "Error Type", "BuiltInTypeClass", "UserTypeClass", "UserTypeClassInst", "CompositeTypeClass", "inferred", "and", "or", "not", "any", "static", "TypeFromExpr", "FieldAccessor", @@ -539,7 +539,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = add(result, typeToString(t.sons[i])) if i < sonsLen(t) - 1: add(result, ", ") add(result, ')') - of tyPtr, tyRef, tyVar: + of tyPtr, tyRef, tyVar, tyLent: result = typeToStr[t.kind] if t.len >= 2: setLen(result, result.len-1) @@ -581,6 +581,8 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = if len(prag) != 0: add(result, "{." & prag & ".}") of tyVarargs: result = typeToStr[t.kind] % typeToString(t.sons[0]) + of tySink: + result = "sink " & typeToString(t.sons[0]) else: result = typeToStr[t.kind] result.addTypeFlags(t) @@ -968,7 +970,7 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool = if result and ExactGenericParams in c.flags: result = a.sym.position == b.sym.position of tyGenericInvocation, tyGenericBody, tySequence, - tyOpenArray, tySet, tyRef, tyPtr, tyVar, + tyOpenArray, tySet, tyRef, tyPtr, tyVar, tyLent, tySink, tyArray, tyProc, tyVarargs, tyOrdinal, tyTypeClasses, tyOpt: cycleCheck() if a.kind == tyUserTypeClass and a.n != nil: return a.n == b.n @@ -992,7 +994,7 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool = cycleCheck() result = sameTypeAux(a.lastSon, b.lastSon, c) of tyNone: result = false - of tyUnused, tyOptAsRef, tyUnused1, tyUnused2: internalError("sameFlags") + of tyUnused, tyOptAsRef: internalError("sameFlags") proc sameBackendType*(x, y: PType): bool = var c = initSameTypeClosure() @@ -1101,11 +1103,11 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, if containsOrIncl(marker, typ.id): return var t = skipTypes(typ, abstractInst-{tyTypeDesc}) case t.kind - of tyVar: + of tyVar, tyLent: if kind in {skProc, skFunc, skConst}: return t var t2 = skipTypes(t.sons[0], abstractInst-{tyTypeDesc}) case t2.kind - of tyVar: + of tyVar, tyLent: if taHeap notin flags: result = t2 # ``var var`` is illegal on the heap of tyOpenArray: if kind != skParam: result = t @@ -1143,7 +1145,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, of tyRange: if skipTypes(t.sons[0], abstractInst-{tyTypeDesc}).kind notin {tyChar, tyEnum, tyInt..tyFloat128, tyUInt8..tyUInt32}: result = t - of tyOpenArray, tyVarargs: + of tyOpenArray, tyVarargs, tySink: if kind != skParam: result = t else: result = typeAllowedAux(marker, t.sons[0], skVar, flags) of tySequence, tyOpt: @@ -1174,7 +1176,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, # for now same as error node; we say it's a valid type as it should # prevent cascading errors: result = nil - of tyUnused, tyOptAsRef, tyUnused1, tyUnused2: internalError("typeAllowedAux") + of tyUnused, tyOptAsRef: internalError("typeAllowedAux") proc typeAllowed*(t: PType, kind: TSymKind): PType = # returns 'nil' on success and otherwise the part of the type that is @@ -1322,7 +1324,7 @@ proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt = if typ.callConv == ccClosure: result = 2 * ptrSize else: result = ptrSize a = ptrSize - of tyNil, tyCString, tyString, tySequence, tyPtr, tyRef, tyVar, tyOpenArray: + of tyNil, tyCString, tyString, tySequence, tyPtr, tyRef, tyVar, tyLent, tyOpenArray: let base = typ.lastSon if base == typ or (base.kind == tyTuple and base.size==szIllegalRecursion): result = szIllegalRecursion diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index fb277272b1..bb6c47324d 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -209,6 +209,8 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; else: result = mapTypeToBracket("ref", mRef, t, info) of tyVar: result = mapTypeToBracket("var", mVar, t, info) + of tyLent: result = mapTypeToBracket("lent", mBuiltinType, t, info) + of tySink: result = mapTypeToBracket("sink", mBuiltinType, t, info) of tySequence: result = mapTypeToBracket("seq", mSeq, t, info) of tyOpt: result = mapTypeToBracket("opt", mOpt, t, info) of tyProc: @@ -274,7 +276,7 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; result.add atomicType("static", mNone) if t.n != nil: result.add t.n.copyTree - of tyUnused, tyOptAsRef, tyUnused1, tyUnused2: internalError("mapTypeToAstX") + of tyUnused, tyOptAsRef: internalError("mapTypeToAstX") proc opMapTypeToAst*(t: PType; info: TLineInfo): PNode = result = mapTypeToAstX(t, info, false, true) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 252b7c788b..a22acdff0d 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -30,7 +30,7 @@ import strutils, ast, astalgo, types, msgs, renderer, vmdef, trees, intsets, rodread, magicsys, options, lowerings - +import platform from os import splitFile when hasFFI: @@ -761,6 +761,49 @@ proc genCard(c: PCtx; n: PNode; dest: var TDest) = c.gABC(n, opcCard, dest, tmp) c.freeTemp(tmp) +proc genIntCast(c: PCtx; n: PNode; dest: var TDest) = + const allowedIntegers = {tyInt..tyInt64, tyUInt..tyUInt64, tyChar} + var signedIntegers = {tyInt8..tyInt32} + var unsignedIntegers = {tyUInt8..tyUInt32, tyChar} + let src = n.sons[1].typ.skipTypes(abstractRange)#.kind + let dst = n.sons[0].typ.skipTypes(abstractRange)#.kind + let src_size = src.getSize + + if platform.intSize < 8: + signedIntegers.incl(tyInt) + unsignedIntegers.incl(tyUInt) + if src_size == dst.getSize and src.kind in allowedIntegers and + dst.kind in allowedIntegers: + let tmp = c.genx(n.sons[1]) + var tmp2 = c.getTemp(n.sons[1].typ) + let tmp3 = c.getTemp(n.sons[1].typ) + if dest < 0: dest = c.getTemp(n[0].typ) + proc mkIntLit(ival: int): int = + result = genLiteral(c, newIntTypeNode(nkIntLit, ival, getSysType(tyInt))) + if src.kind in unsignedIntegers and dst.kind in signedIntegers: + # cast unsigned to signed integer of same size + # signedVal = (unsignedVal xor offset) -% offset + let offset = 1 shl (src_size * 8 - 1) + c.gABx(n, opcLdConst, tmp2, mkIntLit(offset)) + c.gABC(n, opcBitxorInt, tmp3, tmp, tmp2) + c.gABC(n, opcSubInt, dest, tmp3, tmp2) + elif src.kind in signedIntegers and dst.kind in unsignedIntegers: + # cast signed to unsigned integer of same size + # unsignedVal = (offset +% signedVal +% 1) and offset + let offset = (1 shl (src_size * 8)) - 1 + c.gABx(n, opcLdConst, tmp2, mkIntLit(offset)) + c.gABx(n, opcLdConst, dest, mkIntLit(offset+1)) + c.gABC(n, opcAddu, tmp3, tmp, dest) + c.gABC(n, opcNarrowU, tmp3, TRegister(src_size*8)) + c.gABC(n, opcBitandInt, dest, tmp3, tmp2) + else: + c.gABC(n, opcAsgnInt, dest, tmp) + c.freeTemp(tmp) + c.freeTemp(tmp2) + c.freeTemp(tmp3) + else: + globalError(n.info, errGenerated, "VM is only allowed to 'cast' between integers of same size") + proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = case m of mAnd: c.genAndOr(n, opcFJmp, dest) @@ -1245,7 +1288,7 @@ proc whichAsgnOpc(n: PNode): TOpcode = opcAsgnStr of tyFloat..tyFloat128: opcAsgnFloat - of tyRef, tyNil, tyVar: + of tyRef, tyNil, tyVar, tyLent: opcAsgnRef else: opcAsgnComplex @@ -1438,7 +1481,7 @@ proc genRdVar(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = cannotEval(n) template needsRegLoad(): untyped = - gfAddrOf notin flags and fitsRegister(n.typ.skipTypes({tyVar})) + gfAddrOf notin flags and fitsRegister(n.typ.skipTypes({tyVar, tyLent})) proc genArrAccess2(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode; flags: TGenFlags) = @@ -1510,7 +1553,7 @@ proc getNullValue(typ: PType, info: TLineInfo): PNode = result = newNodeIT(nkFloatLit, info, t) of tyCString, tyString: result = newNodeIT(nkStrLit, info, t) - of tyVar, tyPointer, tyPtr, tySequence, tyExpr, + of tyVar, tyLent, tyPointer, tyPtr, tySequence, tyExpr, tyStmt, tyTypeDesc, tyStatic, tyRef, tyNil: result = newNodeIT(nkNilLit, info, t) of tyProc: @@ -1844,9 +1887,11 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) = if allowCast in c.features: genConv(c, n, n.sons[1], dest, opcCast) else: - globalError(n.info, errGenerated, "VM is not allowed to 'cast'") + genIntCast(c, n, dest) of nkTypeOfExpr: genTypeLit(c, n.typ, dest) + of nkComesFrom: + discard "XXX to implement for better stack traces" else: globalError(n.info, errGenerated, "cannot generate VM code for " & $n) diff --git a/compiler/vmmarshal.nim b/compiler/vmmarshal.nim index 0939a5953f..5f725994e8 100644 --- a/compiler/vmmarshal.nim +++ b/compiler/vmmarshal.nim @@ -102,7 +102,7 @@ proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet) = else: storeAny(s, t.lastSon, a[i], stored) s.add("]") - of tyRange, tyGenericInst, tyAlias: storeAny(s, t.lastSon, a, stored) + of tyRange, tyGenericInst, tyAlias, tySink: storeAny(s, t.lastSon, a, stored) of tyEnum: # we need a slow linear search because of enums with holes: for e in items(t.n): @@ -275,7 +275,7 @@ proc loadAny(p: var JsonParser, t: PType, next(p) return raiseParseErr(p, "float expected") - of tyRange, tyGenericInst, tyAlias: result = loadAny(p, t.lastSon, tab) + of tyRange, tyGenericInst, tyAlias, tySink: result = loadAny(p, t.lastSon, tab) else: internalError "cannot marshal at compile-time " & t.typeToString diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim index 8881acaddd..e458cad03f 100644 --- a/compiler/wordrecg.nim +++ b/compiler/wordrecg.nim @@ -45,7 +45,7 @@ type wImportc, wExportc, wExportNims, wIncompleteStruct, wRequiresInit, wAlign, wNodecl, wPure, wSideeffect, wHeader, wNosideeffect, wGcSafe, wNoreturn, wMerge, wLib, wDynlib, - wCompilerproc, wProcVar, wBase, wUsed, + wCompilerproc, wCore, wProcVar, wBase, wUsed, wFatal, wError, wWarning, wHint, wLine, wPush, wPop, wDefine, wUndef, wLinedir, wStacktrace, wLinetrace, wLink, wCompile, wLinksys, wDeprecated, wVarargs, wCallconv, wBreakpoint, wDebugger, @@ -131,7 +131,7 @@ const "incompletestruct", "requiresinit", "align", "nodecl", "pure", "sideeffect", "header", "nosideeffect", "gcsafe", "noreturn", "merge", "lib", "dynlib", - "compilerproc", "procvar", "base", "used", + "compilerproc", "core", "procvar", "base", "used", "fatal", "error", "warning", "hint", "line", "push", "pop", "define", "undef", "linedir", "stacktrace", "linetrace", "link", "compile", "linksys", "deprecated", "varargs", diff --git a/config/nim.cfg b/config/nim.cfg index a146c4ebf4..9fddce90a5 100644 --- a/config/nim.cfg +++ b/config/nim.cfg @@ -59,6 +59,9 @@ path="$lib/pure" debugger:off line_dir:off dead_code_elim:on + @if nimHasNilChecks: + nilchecks:off + @end @end @if release: diff --git a/doc/advopt.txt b/doc/advopt.txt index ab10d65ba7..a1210118e3 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -37,6 +37,7 @@ Advanced options: --noMain do not generate a main procedure --genScript generate a compile script (in the 'nimcache' subdirectory named 'compile_$project$scriptext') + --genDeps generate a '.deps' file containing the dependencies --os:SYMBOL set the target operating system (cross-compilation) --cpu:SYMBOL set the target processor (cross-compilation) --debuginfo enables debug information diff --git a/doc/astspec.txt b/doc/astspec.txt index 57f6b9d8c9..6d755c2e20 100644 --- a/doc/astspec.txt +++ b/doc/astspec.txt @@ -918,7 +918,7 @@ This is equivalent to ``var``, but with ``nnkLetSection`` rather than Concrete syntax: .. code-block:: nim - let v = 3 + let a = 3 AST: diff --git a/doc/lib.rst b/doc/lib.rst index 21d3dc8cc7..755c11899f 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -92,6 +92,10 @@ Collections and algorithms * `sequtils `_ This module implements operations for the built-in seq type which were inspired by functional programming languages. +* `sharedtables `_ + Nim shared hash table support. Contains shared tables. +* `sharedlist `_ + Nim shared linked list support. Contains shared singly linked list. String handling @@ -102,6 +106,10 @@ String handling case of a string, splitting a string into substrings, searching for substrings, replacing substrings. +* `strformat `_ + Macro based standard string interpolation / formatting. Inpired by + Python's ```f``-strings. + * `strmisc `_ This module contains uncommon string handling operations that do not fit with the commonly used operations in strutils. @@ -379,6 +387,7 @@ Cryptography and Hashing * `securehash `_ This module implements a sha1 encoder and decoder. + Multimedia support ------------------ @@ -431,6 +440,8 @@ Modules for JS backend * `jsffi `_ Types and macros for easier interaction with JavaScript. +* `asyncjs `_ + Types and macros for writing asynchronous procedures in JavaScript. Deprecated modules ------------------ diff --git a/doc/manual/pragmas.txt b/doc/manual/pragmas.txt index 835b6909da..cd26a9448b 100644 --- a/doc/manual/pragmas.txt +++ b/doc/manual/pragmas.txt @@ -1087,3 +1087,70 @@ In the above example, providing the -d flag causes the symbol ``FooBar`` to be overwritten at compile time, printing out 42. If the ``-d:FooBar=42`` were to be omitted, the default value of 5 would be used. + + +Custom annotations +------------------ +It is possible to define custom typed pragmas. Custom pragmas do not effect +code generation directly, but their presence can be detected by macros. +Custom pragmas are defined using templates annotated with pragma ``pragma``: + +.. code-block:: nim + template dbTable(name: string, table_space: string = nil) {.pragma.} + template dbKey(name: string = nil, primary_key: bool = false) {.pragma.} + template dbForeignKey(t: typedesc) {.pragma.} + template dbIgnore {.pragma.} + + +Consider stylized example of possible Object Relation Mapping (ORM) implementation: + +.. code-block:: nim + const tblspace {.strdefine.} = "dev" # switch for dev, test and prod environments + + type + User {.dbTable("users", tblspace).} = object + id {.dbKey(primary_key = true).}: int + name {.dbKey"full_name".}: string + is_cached {.dbIgnore.}: bool + age: int + + UserProfile {.dbTable("profiles", tblspace).} = object + id {.dbKey(primary_key = true).}: int + user_id {.dbForeignKey: User.}: int + read_access: bool + write_access: bool + admin_acess: bool + +In this example custom pragmas are used to describe how Nim objects are +mapped to the schema of the relational database. Custom pragmas can have +zero or more arguments. In order to pass multiple arguments use one of +template call syntaxes. All arguments are typed and follow standard +overload resolution rules for templates. Therefore, it is possible to have +default values for arguments, pass by name, varargs, etc. + +Custom pragmas can be used in all locations where ordinary pragmas can be +specified. It is possible to annotate procs, templates, type and variable +definitions, statements, etc. + +Macros module includes helpers which can be used to simplify custom pragma +access `hasCustomPragma`, `getCustomPragmaVal`. Please consult macros module +documentation for details. These macros are no magic, they don't do anything +you cannot do yourself by walking AST object representation. + +More examples with custom pragmas: + - Better serialization/deserialization control: + + .. code-block:: nim + type MyObj = object + a {.dontSerialize.}: int + b {.defaultDeserialize: 5.}: int + c {.serializationKey: "_c".}: string + + - Adopting type for gui inspector in a game engine: + + .. code-block:: nim + type MyComponent = object + position {.editable, animatable.}: Vector3 + alpha {.editRange: [0.0..1.0], animatable.}: float32 + + diff --git a/doc/manual/stmts.txt b/doc/manual/stmts.txt index b24ec3b4a6..721b5cff81 100644 --- a/doc/manual/stmts.txt +++ b/doc/manual/stmts.txt @@ -296,6 +296,10 @@ empty ``discard`` statement should be used. For non ordinal types it is not possible to list every possible value and so these always require an ``else`` part. +As case statements perform compile-time exhaustiveness checks, the value in +every ``of`` branch must be known at compile time. This fact is also exploited +to generate more performant code. + As a special semantic extension, an expression in an ``of`` branch of a case statement may evaluate to a set or array constructor; the set or array is then expanded into a list of its elements: diff --git a/doc/manual/types.txt b/doc/manual/types.txt index 56584f2570..1477995dda 100644 --- a/doc/manual/types.txt +++ b/doc/manual/types.txt @@ -41,7 +41,8 @@ These integer types are pre-defined: ``int`` the generic signed integer type; its size is platform dependent and has the same size as a pointer. This type should be used in general. An integer - literal that has no type suffix is of this type. + literal that has no type suffix is of this type if it is in the range + ``low(int32)..high(int32)`` otherwise the literal's type is ``int64``. intXX additional signed integer types of XX bits use this naming scheme diff --git a/doc/tut1.rst b/doc/tut1.rst index 9e6f1ab3c2..f935e79354 100644 --- a/doc/tut1.rst +++ b/doc/tut1.rst @@ -41,7 +41,7 @@ Save this code to the file "greetings.nim". Now compile and run it:: nim compile --run greetings.nim -With the ``--run`` `switch `_ Nim +With the ``--run`` `switch `_ Nim executes the file automatically after compilation. You can give your program command line arguments by appending them after the filename:: @@ -58,7 +58,7 @@ To compile a release version use:: By default the Nim compiler generates a large amount of runtime checks aiming for your debugging pleasure. With ``-d:release`` these checks are `turned off and optimizations are turned on -`_. +`_. Though it should be pretty obvious what the program does, I will explain the syntax: statements which are not indented are executed when the program diff --git a/koch.nim b/koch.nim index cde74966bb..d51b902ee8 100644 --- a/koch.nim +++ b/koch.nim @@ -97,7 +97,7 @@ proc exec(cmd: string, errorcode: int = QuitFailure, additionalPath = "") = if not absolute.isAbsolute: absolute = getCurrentDir() / absolute echo("Adding to $PATH: ", absolute) - putEnv("PATH", prevPath & PathSep & absolute) + putEnv("PATH", (if prevPath.len > 0: prevPath & PathSep else: "") & absolute) echo(cmd) if execShellCmd(cmd) != 0: quit("FAILURE", errorcode) putEnv("PATH", prevPath) @@ -260,7 +260,7 @@ proc buildTools(latest: bool) = " nimsuggest/nimsuggest.nim" let nimgrepExe = "bin/nimgrep".exe - nimexec "c -o:" & nimgrepExe & " tools/nimgrep.nim" + nimexec "c -d:release -o:" & nimgrepExe & " tools/nimgrep.nim" when defined(windows): buildVccTool() #nimexec "c -o:" & ("bin/nimresolve".exe) & " tools/nimresolve.nim" @@ -402,7 +402,7 @@ proc winReleaseArch(arch: string) = template withMingw(path, body) = let prevPath = getEnv("PATH") - putEnv("PATH", path & PathSep & prevPath) + putEnv("PATH", (if path.len > 0: path & PathSep else: "") & prevPath) try: body finally: @@ -441,7 +441,7 @@ template `|`(a, b): string = (if a.len > 0: a else: b) proc tests(args: string) = # we compile the tester with taintMode:on to have a basic # taint mode test :-) - nimexec "cc --taintMode:on tests/testament/tester" + nimexec "cc --taintMode:on --opt:speed tests/testament/tester" # Since tests take a long time (on my machine), and we want to defy Murhpys # law - lets make sure the compiler really is freshly compiled! nimexec "c --lib:lib -d:release --opt:speed compiler/nim.nim" diff --git a/lib/core/allocators.nim b/lib/core/allocators.nim new file mode 100644 index 0000000000..62f5e9756b --- /dev/null +++ b/lib/core/allocators.nim @@ -0,0 +1,35 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +type + Allocator* = ptr object {.inheritable.} + alloc*: proc (a: Allocator; size: int; alignment: int = 8): pointer {.nimcall.} + dealloc*: proc (a: Allocator; p: pointer; size: int) {.nimcall.} + realloc*: proc (a: Allocator; p: pointer; oldSize, newSize: int): pointer {.nimcall.} + +var + currentAllocator {.threadvar.}: Allocator + +proc getCurrentAllocator*(): Allocator = + result = currentAllocator + +proc setCurrentAllocator*(a: Allocator) = + currentAllocator = a + +proc alloc*(size: int; alignment: int = 8): pointer = + let a = getCurrentAllocator() + result = a.alloc(a, size, alignment) + +proc dealloc*(p: pointer; size: int) = + let a = getCurrentAllocator() + a.dealloc(a, p, size) + +proc realloc*(p: pointer; oldSize, newSize: int): pointer = + let a = getCurrentAllocator() + result = a.realloc(a, p, oldSize, newSize) diff --git a/lib/core/macros.nim b/lib/core/macros.nim index ee6c1a09ff..ed9c304fe0 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -21,7 +21,7 @@ type nnkInt16Lit, nnkInt32Lit, nnkInt64Lit, nnkUIntLit, nnkUInt8Lit, nnkUInt16Lit, nnkUInt32Lit, nnkUInt64Lit, nnkFloatLit, nnkFloat32Lit, nnkFloat64Lit, nnkFloat128Lit, nnkStrLit, nnkRStrLit, - nnkTripleStrLit, nnkNilLit, nnkMetaNode, nnkDotCall, + nnkTripleStrLit, nnkNilLit, nnkComesFrom, nnkDotCall, nnkCommand, nnkCall, nnkCallStrLit, nnkInfix, nnkPrefix, nnkPostfix, nnkHiddenCallConv, nnkExprEqExpr, @@ -130,6 +130,7 @@ const nnkLiterals* = {nnkCharLit..nnkNilLit} nnkCallKinds* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand, nnkCallStrLit} + nnkPragmaCallKinds = {nnkExprColonExpr, nnkCall, nnkCallStrLit} proc `!`*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect, deprecated.} ## constructs an identifier from the string `s` @@ -1213,6 +1214,59 @@ macro expandMacros*(body: typed): untyped = result = getAst(inner(body)) echo result.toStrLit +proc customPragmaNode(n: NimNode): NimNode = + expectKind(n, {nnkSym, nnkDotExpr}) + if n.kind == nnkSym: + let sym = n.symbol.getImpl() + sym.expectRoutine() + result = sym.pragma + elif n.kind == nnkDotExpr: + let typDef = getImpl(getTypeInst(n[0]).symbol) + typDef.expectKind(nnkTypeDef) + typDef[2].expectKind(nnkObjectTy) + let recList = typDef[2][2] + for identDefs in recList: + for i in 0 .. identDefs.len - 3: + if identDefs[i].kind == nnkPragmaExpr and + identDefs[i][0].kind == nnkIdent and $identDefs[i][0] == $n[1]: + return identDefs[i][1] + +macro hasCustomPragma*(n: typed, cp: typed{nkSym}): untyped = + ## Expands to `true` if expression `n` which is expected to be `nnkDotExpr` + ## has custom pragma `cp`. + ## + ## .. code-block:: nim + ## template myAttr() {.pragma.} + ## type + ## MyObj = object + ## myField {.myAttr.}: int + ## var o: MyObj + ## assert(o.myField.hasCustomPragma(myAttr) == 0) + let pragmaNode = customPragmaNode(n) + for p in pragmaNode: + if (p.kind == nnkSym and p == cp) or + (p.kind in nnkPragmaCallKinds and p.len > 0 and p[0].kind == nnkSym and p[0] == cp): + return newLit(true) + return newLit(false) + +macro getCustomPragmaVal*(n: typed, cp: typed{nkSym}): untyped = + ## Expands to value of custom pragma `cp` of expression `n` which is expected + ## to be `nnkDotExpr`. + ## + ## .. code-block:: nim + ## template serializationKey(key: string) {.pragma.} + ## type + ## MyObj = object + ## myField {.serializationKey: "mf".}: int + ## var o: MyObj + ## assert(o.myField.getCustomPragmaVal(serializationKey) == "mf") + let pragmaNode = customPragmaNode(n) + for p in pragmaNode: + if p.kind in nnkPragmaCallKinds and p.len > 0 and p[0].kind == nnkSym and p[0] == cp: + return p[1] + return newEmptyNode() + + when not defined(booting): template emit*(e: static[string]): untyped {.deprecated.} = ## accepts a single string argument and treats it as nim code diff --git a/lib/core/refs.nim b/lib/core/refs.nim new file mode 100644 index 0000000000..e1575b68c7 --- /dev/null +++ b/lib/core/refs.nim @@ -0,0 +1,97 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Default ref implementation used by Nim's core. + +# We cannot use the allocator interface here as we require a heap walker to +# exist. Thus we import 'alloc' directly here to get our own heap that is +# all under the GC's control and can use the ``allObjects`` iterator which +# is crucial for the "sweep" phase. +import typelayouts, alloc + +type + TracingGc = ptr object of Allocator + visit*: proc (fieldAddr: ptr pointer; a: Allocator) {.nimcall.} + + GcColor = enum + white = 0, black = 1, grey = 2 ## to flip the meaning of white/black + ## perform (1 - col) + + GcHeader = object + t: ptr TypeLayout + color: GcColor + Cell = ptr GcHeader + + GcFrame {.core.} = object + prev: ptr GcFrame + marker: proc (self: GcFrame; a: Allocator) + + Phase = enum + None, Marking, Sweeping + + GcHeap = object + r: MemRegion + phase: Phase + currBlack, currWhite: GcColor + greyStack: seq[Cell] + +var + gch {.threadvar.}: GcHeap + +proc `=trace`[T](a: ref T) = + if not marked(a): + mark(a) + `=trace`(a[]) + +template usrToCell(p: pointer): Cell = + +template cellToUsr(cell: Cell): pointer = + cast[pointer](cast[ByteAddress](cell)+%ByteAddress(sizeof(GcHeader))) + +template usrToCell(usr: pointer): Cell = + cast[Cell](cast[ByteAddress](usr)-%ByteAddress(sizeof(GcHeader))) + +template markGrey(x: Cell) = + if x.color == gch.currWhite and phase == Marking: + x.color = grey + add(gch.greyStack, x) + +proc `=`[T](dest: var ref T; src: ref T) = + ## full write barrier implementation. + if src != nil: + let s = usrToCell(src) + markGrey(s) + system.`=`(dest, src) + +proc linkGcFrame(f: ptr GcFrame) {.core.} +proc unlinkGcFrame() {.core.} + +proc setGcFrame(f: ptr GcFrame) {.core.} + +proc registerGlobal(p: pointer; t: ptr TypeLayout) {.core.} +proc unregisterGlobal(p: pointer; t: ptr TypeLayout) {.core.} + +proc registerThreadvar(p: pointer; t: ptr TypeLayout) {.core.} +proc unregisterThreadvar(p: pointer; t: ptr TypeLayout) {.core.} + +proc newImpl(t: ptr TypeLayout): pointer = + let r = cast[Cell](rawAlloc(t.size + sizeof(GcHeader))) + r.typ = t + result = r +! sizeof(GcHeader) + +template new*[T](x: var ref T) = + x = newImpl(getTypeLayout(x)) + + +when false: + # implement these if your GC requires them: + proc writeBarrierLocal() {.core.} + proc writeBarrierGlobal() {.core.} + + proc writeBarrierGeneric() {.core.} diff --git a/lib/core/seqs.nim b/lib/core/seqs.nim new file mode 100644 index 0000000000..c32cf3690b --- /dev/null +++ b/lib/core/seqs.nim @@ -0,0 +1,139 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +import allocators, typetraits + +## Default seq implementation used by Nim's core. +type + seq*[T] = object + len, cap: int + data: ptr UncheckedArray[T] + +template frees(s) = dealloc(s.data, s.cap * sizeof(T)) + +# XXX make code memory safe for overflows in '*' +proc nimSeqLiteral[T](x: openArray[T]): seq[T] {.core.} = + seq[T](len: x.len, cap: x.len, data: x) + +when defined(nimHasTrace): + proc `=trace`[T](s: seq[T]; a: Allocator) = + for i in 0 ..< s.len: `=trace`(s.data[i], a) + +proc `=destroy`[T](x: var seq[T]) = + if x.data != nil: + when not supportsCopyMem(T): + for i in 0..= x.cap: resize(x) + result = addr(x.data[x.len]) + inc x.len + +template add*[T](x: var seq[T]; y: T) = + reserveSlot(x)[] = y + +proc shrink*[T](x: var seq[T]; newLen: int) = + assert newLen <= x.len + assert newLen >= 0 + when not supportsCopyMem(T): + for i in countdown(x.len - 1, newLen - 1): + `=destroy`(x.data[i]) + x.len = newLen + +proc grow*[T](x: var seq[T]; newLen: int; value: T) = + if newLen <= x.len: return + assert newLen >= 0 + if x.cap == 0: x.cap = newLen + else: x.cap = max(newLen, (x.cap * 3) shr 1) + x.data = cast[type(x.data)](realloc(x.data, x.cap * sizeof(T))) + for i in x.len..= s.cap: resize(s) + s.data[s.len] = c + s.data[s.len+1] = '\0' + inc s.len + +proc ensure(s: var string; newLen: int) = + let old = s.cap + if newLen >= old: + s.cap = max((old * 3) shr 1, newLen) + if s.cap > 0: + s.data = cast[type(s.data)](realloc(s.data, old + 1, s.cap + 1)) + +proc add*(s: var string; y: string) = + if y.len != 0: + let newLen = s.len + y.len + ensure(s, newLen) + copyMem(addr s.data[len], y.data, y.data.len + 1) + s.len = newLen + +proc len*(s: string): int {.inline.} = s.len + +proc newString*(len: int): string = + result.len = len + result.cap = len + if len > 0: + result.data = alloc0(len+1) + +converter toCString(x: string): cstring {.core.} = + if x.len == 0: cstring"" else: cast[cstring](x.data) + +proc newStringOfCap*(cap: int): string = + result.len = 0 + result.cap = cap + if cap > 0: + result.data = alloc(cap+1) + +proc `&`*(a, b: string): string = + let sum = a.len + b.len + result = newStringOfCap(sum) + result.len = sum + copyMem(addr result.data[0], a.data, a.len) + copyMem(addr result.data[a.len], b.data, b.len) + if sum > 0: + result.data[sum] = '\0' + +proc concat(x: openArray[string]): string {.core.} = + ## used be the code generator to optimize 'x & y & z ...' + var sum = 0 + for i in 0 ..< x.len: inc(sum, x[i].len) + result = newStringOfCap(sum) + sum = 0 + for i in 0 ..< x.len: + let L = x[i].len + copyMem(addr result.data[sum], x[i].data, L) + inc(sum, L) + diff --git a/lib/core/typelayouts.nim b/lib/core/typelayouts.nim new file mode 100644 index 0000000000..445ce77c48 --- /dev/null +++ b/lib/core/typelayouts.nim @@ -0,0 +1,19 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +type + TypeLayout* = object + size*, alignment*: int + destructor*: proc (self: pointer; a: Allocator) {.nimcall.} + trace*: proc (self: pointer; a: Allocator) {.nimcall.} + when false: + construct*: proc (self: pointer; a: Allocator) {.nimcall.} + copy*, deepcopy*, sink*: proc (self, other: pointer; a: Allocator) {.nimcall.} + +proc getTypeLayout(t: typedesc): ptr TypeLayout {.magic: "getTypeLayout".} diff --git a/lib/js/asyncjs.nim b/lib/js/asyncjs.nim new file mode 100644 index 0000000000..ec410ee39a --- /dev/null +++ b/lib/js/asyncjs.nim @@ -0,0 +1,141 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim Authors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. + +## This module implements types and macros for writing asynchronous code +## for the JS backend. It provides tools for interaction with JavaScript async API-s +## and libraries, writing async procedures in Nim and converting callback-based code +## to promises. +## +## A Nim procedure is asynchronous when it includes the ``{.async.}`` pragma. It +## should always have a ``Future[T]`` return type or not have a return type at all. +## A ``Future[void]`` return type is assumed by default. +## +## This is roughly equivalent to the ``async`` keyword in JavaScript code. +## +## .. code-block:: nim +## proc loadGame(name: string): Future[Game] {.async.} = +## # code +## +## should be equivalent to +## +## .. code-block:: javascript +## async function loadGame(name) { +## // code +## } +## +## A call to an asynchronous procedure usually needs ``await`` to wait for +## the completion of the ``Future``. +## +## .. code-block:: nim +## var game = await loadGame(name) +## +## Often, you might work with callback-based API-s. You can wrap them with +## asynchronous procedures using promises and ``newPromise``: +## +## .. code-block:: nim +## proc loadGame(name: string): Future[Game] = +## var promise = newPromise() do (resolve: proc(response: Game)): +## cbBasedLoadGame(name) do (game: Game): +## resolve(game) +## return promise +## +## Forward definitions work properly, you just need to always add the ``{.async.}`` pragma: +## +## .. code-block:: nim +## proc loadGame(name: string): Future[Game] {.async.} +## +## JavaScript compatibility +## ~~~~~~~~~~~~~~~~~~~~~~~~~ +## +## Nim currently generates `async/await` JavaScript code which is supported in modern +## EcmaScript and most modern versions of browsers, Node.js and Electron. +## If you need to use this module with older versions of JavaScript, you can +## use a tool that backports the resulting JavaScript code, as babel. + +import jsffi +import macros + +when not defined(js) and not defined(nimdoc) and not defined(nimsuggest): + {.fatal: "Module asyncjs is designed to be used with the JavaScript backend.".} + +type + Future*[T] = ref object + future*: T + ## Wraps the return type of an asynchronous procedure. + + PromiseJs* {.importcpp: "Promise".} = ref object + ## A JavaScript Promise + +proc replaceReturn(node: var NimNode) = + var z = 0 + for s in node: + var son = node[z] + if son.kind == nnkReturnStmt: + node[z] = nnkReturnStmt.newTree(nnkCall.newTree(ident("jsResolve"), son[0])) + elif son.kind == nnkAsgn and son[0].kind == nnkIdent and $son[0] == "result": + node[z] = nnkAsgn.newTree(son[0], nnkCall.newTree(ident("jsResolve"), son[1])) + else: + replaceReturn(son) + inc z + +proc isFutureVoid(node: NimNode): bool = + result = node.kind == nnkBracketExpr and + node[0].kind == nnkIdent and $node[0] == "Future" and + node[1].kind == nnkIdent and $node[1] == "void" + +proc generateJsasync(arg: NimNode): NimNode = + assert arg.kind == nnkProcDef + result = arg + var isVoid = false + var jsResolveNode = ident("jsResolve") + + if arg.params[0].kind == nnkEmpty: + result.params[0] = nnkBracketExpr.newTree(ident("Future"), ident("void")) + isVoid = true + elif isFutureVoid(arg.params[0]): + isVoid = true + + var code = result.body + replaceReturn(code) + result.body = nnkStmtList.newTree() + + if len(code) > 0: + var awaitFunction = quote: + proc await[T](f: Future[T]): T {.importcpp: "(await #)".} + result.body.add(awaitFunction) + + var resolve: NimNode + if isVoid: + resolve = quote: + var `jsResolveNode` {.importcpp: "undefined".}: Future[void] + else: + resolve = quote: + proc jsResolve[T](a: T): Future[T] {.importcpp: "#".} + result.body.add(resolve) + else: + result.body = newEmptyNode() + for child in code: + result.body.add(child) + + if len(code) > 0 and isVoid: + var voidFix = quote: + return `jsResolveNode` + result.body.add(voidFix) + + result.pragma = quote: + {.codegenDecl: "async function $2($3)".} + + +macro async*(arg: untyped): untyped = + ## Macro which converts normal procedures into + ## javascript-compatible async procedures + generateJsasync(arg) + +proc newPromise*[T](handler: proc(resolve: proc(response: T))): Future[T] {.importcpp: "(new Promise(#))".} + ## A helper for wrapping callback-based functions + ## into promises and async procedures diff --git a/lib/js/dom.nim b/lib/js/dom.nim index cdefc772c5..55692d47dd 100644 --- a/lib/js/dom.nim +++ b/lib/js/dom.nim @@ -134,9 +134,9 @@ type # https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement HtmlElement* = ref object of Element - contentEditable*: string + contentEditable*: cstring isContentEditable*: bool - dir*: string + dir*: cstring offsetHeight*: int offsetWidth*: int offsetLeft*: int @@ -405,7 +405,7 @@ type # EventTarget "methods" proc addEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), useCapture: bool = false) proc addEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), options: AddEventListenerOptions) - +proc removeEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), useCapture: bool = false) # Window "methods" proc alert*(w: Window, msg: cstring) @@ -507,6 +507,7 @@ proc replace*(loc: Location, s: cstring) proc back*(h: History) proc forward*(h: History) proc go*(h: History, pagesToJump: int) +proc pushState*[T](h: History, stateObject: T, title, url: cstring) # Navigator "methods" proc javaEnabled*(h: Navigator): bool diff --git a/lib/nimbase.h b/lib/nimbase.h index ac2cc097c1..69699a2a4b 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -70,7 +70,7 @@ __clang__ #if defined(_MSC_VER) # pragma warning(disable: 4005 4100 4101 4189 4191 4200 4244 4293 4296 4309) # pragma warning(disable: 4310 4365 4456 4477 4514 4574 4611 4668 4702 4706) -# pragma warning(disable: 4710 4711 4774 4800 4820 4996 4090 4297) +# pragma warning(disable: 4710 4711 4774 4800 4809 4820 4996 4090 4297) #endif /* ------------------------------------------------------------------------- */ @@ -159,6 +159,7 @@ __clang__ /* ------------------------------------------------------------------- */ #if defined(WIN32) || defined(_WIN32) /* only Windows has this mess... */ +# define N_LIB_PRIVATE # define N_CDECL(rettype, name) rettype __cdecl name # define N_STDCALL(rettype, name) rettype __stdcall name # define N_SYSCALL(rettype, name) rettype __syscall name @@ -178,6 +179,7 @@ __clang__ # endif # define N_LIB_IMPORT extern __declspec(dllimport) #else +# define N_LIB_PRIVATE __attribute__((visibility("hidden"))) # if defined(__GNUC__) # define N_CDECL(rettype, name) rettype name # define N_STDCALL(rettype, name) rettype name @@ -398,11 +400,11 @@ typedef struct TStringDesc* string; // NAN definition copied from math.h included in the Windows SDK version 10.0.14393.0 #ifndef NAN -#ifndef _HUGE_ENUF -#define _HUGE_ENUF 1e+300 // _HUGE_ENUF*_HUGE_ENUF must overflow -#endif -#define NAN_INFINITY ((float)(_HUGE_ENUF * _HUGE_ENUF)) -#define NAN ((float)(NAN_INFINITY * 0.0F)) +# ifndef _HUGE_ENUF +# define _HUGE_ENUF 1e+300 // _HUGE_ENUF*_HUGE_ENUF must overflow +# endif +# define NAN_INFINITY ((float)(_HUGE_ENUF * _HUGE_ENUF)) +# define NAN ((float)(NAN_INFINITY * 0.0F)) #endif #ifndef INF @@ -480,7 +482,6 @@ static inline void GCGuard (void *ptr) { asm volatile ("" :: "X" (ptr)); } On disagreement, your C compiler will say something like: "error: 'Nim_and_C_compiler_disagree_on_target_architecture' declared as an array with a negative size" */ typedef int Nim_and_C_compiler_disagree_on_target_architecture[sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8 ? 1 : -1]; -#endif #ifdef __cplusplus # define NIM_EXTERNC extern "C" @@ -507,3 +508,5 @@ extern Libc::Env *genodeEnv; /* Compile with -d:checkAbi and a sufficiently C11:ish compiler to enable */ #define NIM_CHECK_SIZE(typ, sz) \ _Static_assert(sizeof(typ) == sz, "Nim & C disagree on type size") + +#endif /* NIMBASE_H */ diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index b635c0b0b1..fba35868ca 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -609,11 +609,12 @@ proc clock_nanosleep*(a1: ClockId, a2: cint, a3: var Timespec, proc clock_settime*(a1: ClockId, a2: var Timespec): cint {. importc, header: "".} +proc `==`*(a, b: Time): bool {.borrow.} +proc `-`*(a, b: Time): Time {.borrow.} proc ctime*(a1: var Time): cstring {.importc, header: "".} proc ctime_r*(a1: var Time, a2: cstring): cstring {.importc, header: "".} proc difftime*(a1, a2: Time): cdouble {.importc, header: "".} proc getdate*(a1: cstring): ptr Tm {.importc, header: "".} - proc gmtime*(a1: var Time): ptr Tm {.importc, header: "".} proc gmtime_r*(a1: var Time, a2: var Tm): ptr Tm {.importc, header: "".} proc localtime*(a1: var Time): ptr Tm {.importc, header: "".} diff --git a/lib/posix/posix_linux_amd64.nim b/lib/posix/posix_linux_amd64.nim index c44128b16c..9e6211b633 100644 --- a/lib/posix/posix_linux_amd64.nim +++ b/lib/posix/posix_linux_amd64.nim @@ -12,8 +12,6 @@ # To be included from posix.nim! -from times import Time - const hasSpawnH = not defined(haiku) # should exist for every Posix system nowadays hasAioH = defined(linux) @@ -40,13 +38,15 @@ type const SIG_HOLD* = cast[SigHandler](2) type + Time* {.importc: "time_t", header: "".} = distinct clong + Timespec* {.importc: "struct timespec", header: "", final, pure.} = object ## struct timespec tv_sec*: Time ## Seconds. tv_nsec*: clong ## Nanoseconds. Dirent* {.importc: "struct dirent", - header: "", final, pure.} = object ## dirent_t struct + header: "", final, pure.} = object ## dirent_t struct d_ino*: Ino d_off*: Off d_reclen*: cushort diff --git a/lib/posix/posix_other.nim b/lib/posix/posix_other.nim index 7321889a8f..01bc1c1e5e 100644 --- a/lib/posix/posix_other.nim +++ b/lib/posix/posix_other.nim @@ -9,8 +9,6 @@ {.deadCodeElim:on.} -from times import Time - const hasSpawnH = not defined(haiku) # should exist for every Posix system nowadays hasAioH = defined(linux) @@ -36,6 +34,8 @@ type {.deprecated: [TSocketHandle: SocketHandle].} type + Time* {.importc: "time_t", header: "".} = distinct clong + Timespec* {.importc: "struct timespec", header: "", final, pure.} = object ## struct timespec tv_sec*: Time ## Seconds. @@ -209,24 +209,24 @@ type st_gid*: Gid ## Group ID of file. st_rdev*: Dev ## Device ID (if file is character or block special). st_size*: Off ## For regular files, the file size in bytes. - ## For symbolic links, the length in bytes of the - ## pathname contained in the symbolic link. - ## For a shared memory object, the length in bytes. - ## For a typed memory object, the length in bytes. - ## For other file types, the use of this field is - ## unspecified. + ## For symbolic links, the length in bytes of the + ## pathname contained in the symbolic link. + ## For a shared memory object, the length in bytes. + ## For a typed memory object, the length in bytes. + ## For other file types, the use of this field is + ## unspecified. when defined(macosx) or defined(android): - st_atime*: Time ## Time of last access. - st_mtime*: Time ## Time of last data modification. - st_ctime*: Time ## Time of last status change. + st_atime*: Time ## Time of last access. + st_mtime*: Time ## Time of last data modification. + st_ctime*: Time ## Time of last status change. else: - st_atim*: Timespec ## Time of last access. - st_mtim*: Timespec ## Time of last data modification. - st_ctim*: Timespec ## Time of last status change. - st_blksize*: Blksize ## A file system-specific preferred I/O block size - ## for this object. In some file system types, this - ## may vary from file to file. - st_blocks*: Blkcnt ## Number of blocks allocated for this object. + st_atim*: Timespec ## Time of last access. + st_mtim*: Timespec ## Time of last data modification. + st_ctim*: Timespec ## Time of last status change. + st_blksize*: Blksize ## A file system-specific preferred I/O block size + ## for this object. In some file system types, this + ## may vary from file to file. + st_blocks*: Blkcnt ## Number of blocks allocated for this object. Statvfs* {.importc: "struct statvfs", header: "", diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index d48274bb99..0539706a93 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -168,18 +168,20 @@ type timers*: HeapQueue[tuple[finishAt: float, fut: Future[void]]] callbacks*: Deque[proc ()] -proc processTimers(p: PDispatcherBase) {.inline.} = +proc processTimers(p: PDispatcherBase; didSomeWork: var bool) {.inline.} = #Process just part if timers at a step var count = p.timers.len let t = epochTime() while count > 0 and t >= p.timers[0].finishAt: p.timers.pop().fut.complete() dec count + didSomeWork = true -proc processPendingCallbacks(p: PDispatcherBase) = +proc processPendingCallbacks(p: PDispatcherBase; didSomeWork: var bool) = while p.callbacks.len > 0: var cb = p.callbacks.popFirst() cb() + didSomeWork = true proc adjustedTimeout(p: PDispatcherBase, timeout: int): int {.inline.} = # If dispatcher has active timers this proc returns the timeout @@ -298,14 +300,13 @@ when defined(windows) or defined(nimdoc): let p = getGlobalDispatcher() p.handles.len != 0 or p.timers.len != 0 or p.callbacks.len != 0 - proc poll*(timeout = 500) = - ## Waits for completion events and processes them. Raises ``ValueError`` - ## if there are no pending operations. + proc runOnce(timeout = 500): bool = let p = getGlobalDispatcher() if p.handles.len == 0 and p.timers.len == 0 and p.callbacks.len == 0: raise newException(ValueError, "No handles or timers registered in dispatcher.") + result = false if p.handles.len != 0: let at = p.adjustedTimeout(timeout) var llTimeout = @@ -318,6 +319,7 @@ when defined(windows) or defined(nimdoc): let res = getQueuedCompletionStatus(p.ioPort, addr lpNumberOfBytesTransferred, addr lpCompletionKey, cast[ptr POVERLAPPED](addr customOverlapped), llTimeout).bool + result = true # http://stackoverflow.com/a/12277264/492186 # TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html @@ -347,13 +349,14 @@ when defined(windows) or defined(nimdoc): else: if errCode.int32 == WAIT_TIMEOUT: # Timed out - discard + result = false else: raiseOSError(errCode) # Timer processing. - processTimers(p) + processTimers(p, result) # Callback queue processing - processPendingCallbacks(p) + processPendingCallbacks(p, result) + var acceptEx: WSAPROC_ACCEPTEX var connectEx: WSAPROC_CONNECTEX @@ -1229,7 +1232,7 @@ else: # descriptor was unregistered in callback via `unregister()`. discard - proc poll*(timeout = 500) = + proc runOnce(timeout = 500): bool = let p = getGlobalDispatcher() when ioselSupportedPlatform: let customSet = {Event.Timer, Event.Signal, Event.Process, @@ -1239,6 +1242,7 @@ else: raise newException(ValueError, "No handles or timers registered in dispatcher.") + result = false if not p.selector.isEmpty(): var keys: array[64, ReadyKey] var count = p.selector.selectInto(p.adjustedTimeout(timeout), keys) @@ -1251,20 +1255,24 @@ else: if Event.Read in events or events == {Event.Error}: processBasicCallbacks(fd, readList) + result = true if Event.Write in events or events == {Event.Error}: processBasicCallbacks(fd, writeList) + result = true - if Event.User in events or events == {Event.Error}: + if Event.User in events: processBasicCallbacks(fd, readList) custom = true if rLength == 0: p.selector.unregister(fd) + result = true when ioselSupportedPlatform: if (customSet * events) != {}: custom = true processCustomCallbacks(fd) + result = true # because state `data` can be modified in callback we need to update # descriptor events with currently registered callbacks. @@ -1276,9 +1284,9 @@ else: p.selector.updateHandle(SocketHandle(fd), newEvents) # Timer processing. - processTimers(p) + processTimers(p, result) # Callback queue processing - processPendingCallbacks(p) + processPendingCallbacks(p, result) proc recv*(socket: AsyncFD, size: int, flags = {SocketFlag.SafeDisconn}): Future[string] = @@ -1501,6 +1509,19 @@ else: data.readList.add(cb) p.selector.registerEvent(SelectEvent(ev), data) +proc drain*(timeout = 500) = + ## Waits for completion events and processes them. Raises ``ValueError`` + ## if there are no pending operations. In contrast to ``poll`` this + ## processes as many events as are available. + if runOnce(timeout): + while hasPendingOperations() and runOnce(0): discard + +proc poll*(timeout = 500) = + ## Waits for completion events and processes them. Raises ``ValueError`` + ## if there are no pending operations. This runs the underlying OS + ## `epoll`:idx: or `kqueue`:idx: primitive only once. + discard runOnce(timeout) + # Common procedures between current and upcoming asyncdispatch include includes.asynccommon diff --git a/lib/pure/asyncfutures.nim b/lib/pure/asyncfutures.nim index 82bc53aaff..6df6527d59 100644 --- a/lib/pure/asyncfutures.nim +++ b/lib/pure/asyncfutures.nim @@ -1,4 +1,4 @@ -import os, tables, strutils, times, heapqueue, options, deques +import os, tables, strutils, times, heapqueue, options, deques, cstrutils # TODO: This shouldn't need to be included, but should ideally be exported. type @@ -217,17 +217,78 @@ proc `callback=`*[T](future: Future[T], ## If future has already completed then ``cb`` will be called immediately. future.callback = proc () = cb(future) -proc injectStacktrace[T](future: Future[T]) = - # TODO: Come up with something better. - when not defined(release): - var msg = "" - msg.add("\n " & future.fromProc & "'s lead up to read of failed Future:") +proc getHint(entry: StackTraceEntry): string = + ## We try to provide some hints about stack trace entries that the user + ## may not be familiar with, in particular calls inside the stdlib. + result = "" + if entry.procname == "processPendingCallbacks": + if cmpIgnoreStyle(entry.filename, "asyncdispatch.nim") == 0: + return "Executes pending callbacks" + elif entry.procname == "poll": + if cmpIgnoreStyle(entry.filename, "asyncdispatch.nim") == 0: + return "Processes asynchronous completion events" - if not future.errorStackTrace.isNil and future.errorStackTrace != "": - msg.add("\n" & indent(future.errorStackTrace.strip(), 4)) - else: - msg.add("\n Empty or nil stack trace.") - future.error.msg.add(msg) + if entry.procname.endsWith("_continue"): + if cmpIgnoreStyle(entry.filename, "asyncmacro.nim") == 0: + return "Resumes an async procedure" + +proc `$`*(entries: seq[StackTraceEntry]): string = + result = "" + # Find longest filename & line number combo for alignment purposes. + var longestLeft = 0 + for entry in entries: + if entry.procName.isNil: continue + + let left = $entry.filename & $entry.line + if left.len > longestLeft: + longestLeft = left.len + + var indent = 2 + # Format the entries. + for entry in entries: + if entry.procName.isNil: + if entry.line == -10: + result.add(spaces(indent) & "#[\n") + indent.inc(2) + else: + indent.dec(2) + result.add(spaces(indent)& "]#\n") + continue + + let left = "$#($#)" % [$entry.filename, $entry.line] + result.add((spaces(indent) & "$#$# $#\n") % [ + left, + spaces(longestLeft - left.len + 2), + $entry.procName + ]) + let hint = getHint(entry) + if hint.len > 0: + result.add(spaces(indent+2) & "## " & hint & "\n") + +proc injectStacktrace[T](future: Future[T]) = + when not defined(release): + const header = "\nAsync traceback:\n" + + var exceptionMsg = future.error.msg + if header in exceptionMsg: + # This is messy: extract the original exception message from the msg + # containing the async traceback. + let start = exceptionMsg.find(header) + exceptionMsg = exceptionMsg[0.. createCb(retFuture) - #var cbName = newIdentNode("cb") + # NOTE: The "_continue" suffix is checked for in asyncfutures.nim to produce + # friendlier stack traces: + var cbName = genSym(nskProc, prcName & "_continue") var procCb = getAst createCb(retFutureSym, iteratorNameSym, newStrLitNode(prcName), + cbName, createFutureVarCompletions(futureVarIdents, nil)) outerProcBody.add procCb diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index 650bb3340b..adee34b9e3 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -286,6 +286,7 @@ template readInto(buf: pointer, size: int, socket: AsyncSocket, flags: set[SocketFlag]): int = ## Reads **up to** ``size`` bytes from ``socket`` into ``buf``. Note that ## this is a template and not a proc. + assert(not socket.closed, "Cannot `recv` on a closed socket") var res = 0 if socket.isSsl: when defineSsl: @@ -412,6 +413,7 @@ proc send*(socket: AsyncSocket, buf: pointer, size: int, ## Sends ``size`` bytes from ``buf`` to ``socket``. The returned future will complete once all ## data has been sent. assert socket != nil + assert(not socket.closed, "Cannot `send` on a closed socket") if socket.isSsl: when defineSsl: sslLoop(socket, flags, diff --git a/lib/pure/collections/critbits.nim b/lib/pure/collections/critbits.nim index 19f1f2e58d..34f5c54708 100644 --- a/lib/pure/collections/critbits.nim +++ b/lib/pure/collections/critbits.nim @@ -141,8 +141,8 @@ proc excl*[T](c: var CritBitTree[T], key: string) = proc missingOrExcl*[T](c: var CritBitTree[T], key: string): bool = ## Returns true iff `c` does not contain the given `key`. If the key - ## does exist, c.excl(key) is performed. - let oldCount = c.count + ## does exist, c.excl(key) is performed. + let oldCount = c.count var n = exclImpl(c, key) result = c.count == oldCount @@ -326,7 +326,7 @@ proc `$`*[T](c: CritBitTree[T]): string = result.add($key) when T isnot void: result.add(": ") - result.add($val) + result.addQuoted(val) result.add("}") when isMainModule: diff --git a/lib/pure/collections/deques.nim b/lib/pure/collections/deques.nim index 1e0cb82d2d..328308a9b3 100644 --- a/lib/pure/collections/deques.nim +++ b/lib/pure/collections/deques.nim @@ -185,7 +185,7 @@ proc `$`*[T](deq: Deque[T]): string = result = "[" for x in deq: if result.len > 1: result.add(", ") - result.add($x) + result.addQuoted(x) result.add("]") when isMainModule: diff --git a/lib/pure/collections/lists.nim b/lib/pure/collections/lists.nim index 560273dfa3..e69acc8d95 100644 --- a/lib/pure/collections/lists.nim +++ b/lib/pure/collections/lists.nim @@ -135,7 +135,7 @@ proc `$`*[T](L: SomeLinkedCollection[T]): string = result = "[" for x in nodes(L): if result.len > 1: result.add(", ") - result.add($x.value) + result.addQuoted(x.value) result.add("]") proc find*[T](L: SomeLinkedCollection[T], value: T): SomeLinkedNode[T] = diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index f936b3ecaa..9e9152fc8c 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -406,7 +406,7 @@ template dollarImpl() {.dirty.} = result = "{" for key in items(s): if result.len > 1: result.add(", ") - result.add($key) + result.addQuoted(key) result.add("}") proc `$`*[A](s: HashSet[A]): string = diff --git a/lib/pure/collections/sharedlist.nim b/lib/pure/collections/sharedlist.nim index e93ceb02fc..b3e677b79c 100644 --- a/lib/pure/collections/sharedlist.nim +++ b/lib/pure/collections/sharedlist.nim @@ -73,10 +73,10 @@ proc add*[A](x: var SharedList[A]; y: A) = node.d[node.dataLen] = y inc(node.dataLen) -proc initSharedList*[A](): SharedList[A] = - initLock result.lock - result.head = nil - result.tail = nil +proc init*[A](t: var SharedList[A]) = + initLock t.lock + t.head = nil + t.tail = nil proc clear*[A](t: var SharedList[A]) = withLock(t): @@ -92,4 +92,11 @@ proc deinitSharedList*[A](t: var SharedList[A]) = clear(t) deinitLock t.lock +proc initSharedList*[A](): SharedList[A] {.deprecated.} = + ## Deprecated. Use `init` instead. + ## This is not posix compliant, may introduce undefined behavior. + initLock result.lock + result.head = nil + result.tail = nil + {.pop.} diff --git a/lib/pure/collections/sharedtables.nim b/lib/pure/collections/sharedtables.nim index fc50ea41c7..4f311af874 100644 --- a/lib/pure/collections/sharedtables.nim +++ b/lib/pure/collections/sharedtables.nim @@ -183,6 +183,7 @@ proc `[]=`*[A, B](t: var SharedTable[A, B], key: A, val: B) = proc add*[A, B](t: var SharedTable[A, B], key: A, val: B) = ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. + ## This can introduce duplicate keys into the table! withLock t: addImpl(enlarge) @@ -191,19 +192,29 @@ proc del*[A, B](t: var SharedTable[A, B], key: A) = withLock t: delImpl() -proc initSharedTable*[A, B](initialSize=64): SharedTable[A, B] = +proc init*[A, B](t: var SharedTable[A, B], initialSize=64) = ## creates a new hash table that is empty. ## ## `initialSize` needs to be a power of two. If you need to accept runtime ## values for this you could use the ``nextPowerOfTwo`` proc from the ## `math `_ module or the ``rightSize`` proc from this module. assert isPowerOfTwo(initialSize) - result.counter = 0 - result.dataLen = initialSize - result.data = cast[KeyValuePairSeq[A, B]](allocShared0( + t.counter = 0 + t.dataLen = initialSize + t.data = cast[KeyValuePairSeq[A, B]](allocShared0( sizeof(KeyValuePair[A, B]) * initialSize)) - initLock result.lock + initLock t.lock proc deinitSharedTable*[A, B](t: var SharedTable[A, B]) = deallocShared(t.data) deinitLock t.lock + +proc initSharedTable*[A, B](initialSize=64): SharedTable[A, B] {.deprecated.} = + ## Deprecated. Use `init` instead. + ## This is not posix compliant, may introduce undefined behavior. + assert isPowerOfTwo(initialSize) + result.counter = 0 + result.dataLen = initialSize + result.data = cast[KeyValuePairSeq[A, B]](allocShared0( + sizeof(KeyValuePair[A, B]) * initialSize)) + initLock result.lock diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 01a42efab4..777beabc39 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -308,6 +308,7 @@ proc `[]=`*[A, B](t: var Table[A, B], key: A, val: B) = proc add*[A, B](t: var Table[A, B], key: A, val: B) = ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. + ## This can introduce duplicate keys into the table! addImpl(enlarge) proc len*[A, B](t: TableRef[A, B]): int = @@ -337,9 +338,9 @@ template dollarImpl(): untyped {.dirty.} = result = "{" for key, val in pairs(t): if result.len > 1: result.add(", ") - result.add($key) + result.addQuoted(key) result.add(": ") - result.add($val) + result.addQuoted(val) result.add("}") proc `$`*[A, B](t: Table[A, B]): string = @@ -430,6 +431,7 @@ proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) = proc add*[A, B](t: TableRef[A, B], key: A, val: B) = ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. + ## This can introduce duplicate keys into the table! t[].add(key, val) proc del*[A, B](t: TableRef[A, B], key: A) = @@ -604,6 +606,7 @@ proc `[]=`*[A, B](t: var OrderedTable[A, B], key: A, val: B) = proc add*[A, B](t: var OrderedTable[A, B], key: A, val: B) = ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. + ## This can introduce duplicate keys into the table! addImpl(enlarge) proc mgetOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): var B = @@ -770,6 +773,7 @@ proc `[]=`*[A, B](t: OrderedTableRef[A, B], key: A, val: B) = proc add*[A, B](t: OrderedTableRef[A, B], key: A, val: B) = ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. + ## This can introduce duplicate keys into the table! t[].add(key, val) proc newOrderedTable*[A, B](initialSize=64): OrderedTableRef[A, B] = @@ -962,9 +966,10 @@ proc initCountTable*[A](initialSize=64): CountTable[A] = newSeq(result.data, initialSize) proc toCountTable*[A](keys: openArray[A]): CountTable[A] = - ## creates a new count table with every key in `keys` having a count of 1. + ## creates a new count table with every key in `keys` having a count + ## of how many times it occurs in `keys`. result = initCountTable[A](rightSize(keys.len)) - for key in items(keys): result[key] = 1 + for key in items(keys): result.inc key proc `$`*[A](t: CountTable[A]): string = ## The `$` operator for count tables. @@ -989,9 +994,10 @@ proc inc*[A](t: var CountTable[A], key: A, val = 1) = proc smallest*[A](t: CountTable[A]): tuple[key: A, val: int] = ## returns the (key,val)-pair with the smallest `val`. Efficiency: O(n) assert t.len > 0 - var minIdx = 0 - for h in 1..high(t.data): - if t.data[h].val > 0 and t.data[minIdx].val > t.data[h].val: minIdx = h + var minIdx = -1 + for h in 0..high(t.data): + if t.data[h].val > 0 and (minIdx == -1 or t.data[minIdx].val > t.data[h].val): + minIdx = h result.key = t.data[minIdx].key result.val = t.data[minIdx].val @@ -1325,3 +1331,7 @@ when isMainModule: assert((a == b) == true) assert((b == a) == true) + block: # CountTable.smallest + var t = initCountTable[int]() + for v in items([0, 0, 5, 5, 5]): t.inc(v) + doAssert t.smallest == (0, 2) diff --git a/lib/pure/cookies.nim b/lib/pure/cookies.nim index 07b37c7d48..8f16717ac1 100644 --- a/lib/pure/cookies.nim +++ b/lib/pure/cookies.nim @@ -51,7 +51,7 @@ proc setCookie*(key, value: string, domain = "", path = "", if secure: result.add("; Secure") if httpOnly: result.add("; HttpOnly") -proc setCookie*(key, value: string, expires: TimeInfo, +proc setCookie*(key, value: string, expires: DateTime, domain = "", path = "", noName = false, secure = false, httpOnly = false): string = ## Creates a command in the format of @@ -63,9 +63,9 @@ proc setCookie*(key, value: string, expires: TimeInfo, noname, secure, httpOnly) when isMainModule: - var tim = Time(int(getTime()) + 76 * (60 * 60 * 24)) + var tim = fromUnix(getTime().toUnix + 76 * (60 * 60 * 24)) - let cookie = setCookie("test", "value", tim.getGMTime()) + let cookie = setCookie("test", "value", tim.utc) when not defined(testing): echo cookie let start = "Set-Cookie: test=value; Expires=" diff --git a/lib/pure/cstrutils.nim b/lib/pure/cstrutils.nim new file mode 100644 index 0000000000..4371408925 --- /dev/null +++ b/lib/pure/cstrutils.nim @@ -0,0 +1,79 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module supports helper routines for working with ``cstring`` +## without having to convert ``cstring`` to ``string`` in order to +## save allocations. + +include "system/inclrtl" + +proc toLowerAscii(c: char): char {.inline.} = + if c in {'A'..'Z'}: + result = chr(ord(c) + (ord('a') - ord('A'))) + else: + result = c + +proc startsWith*(s, prefix: cstring): bool {.noSideEffect, + rtl, extern: "csuStartsWith".} = + ## Returns true iff ``s`` starts with ``prefix``. + ## + ## If ``prefix == ""`` true is returned. + var i = 0 + while true: + if prefix[i] == '\0': return true + if s[i] != prefix[i]: return false + inc(i) + +proc endsWith*(s, suffix: cstring): bool {.noSideEffect, + rtl, extern: "csuEndsWith".} = + ## Returns true iff ``s`` ends with ``suffix``. + ## + ## If ``suffix == ""`` true is returned. + let slen = s.len + var i = 0 + var j = slen - len(suffix) + while i+j <% slen: + if s[i+j] != suffix[i]: return false + inc(i) + if suffix[i] == '\0': return true + +proc cmpIgnoreStyle*(a, b: cstring): int {.noSideEffect, + rtl, extern: "csuCmpIgnoreStyle".} = + ## Compares two strings normalized (i.e. case and + ## underscores do not matter). Returns: + ## + ## | 0 iff a == b + ## | < 0 iff a < b + ## | > 0 iff a > b + var i = 0 + var j = 0 + while true: + while a[i] == '_': inc(i) + while b[j] == '_': inc(j) # BUGFIX: typo + var aa = toLowerAscii(a[i]) + var bb = toLowerAscii(b[j]) + result = ord(aa) - ord(bb) + if result != 0 or aa == '\0': break + inc(i) + inc(j) + +proc cmpIgnoreCase*(a, b: cstring): int {.noSideEffect, + rtl, extern: "csuCmpIgnoreCase".} = + ## Compares two strings in a case insensitive manner. Returns: + ## + ## | 0 iff a == b + ## | < 0 iff a < b + ## | > 0 iff a > b + var i = 0 + while true: + var aa = toLowerAscii(a[i]) + var bb = toLowerAscii(b[i]) + result = ord(aa) - ord(bb) + if result != 0 or aa == '\0': break + inc(i) diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 54a8498fa5..aefcff37a7 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -923,8 +923,14 @@ proc parseChunks(client: HttpClient | AsyncHttpClient): Future[void] if chunkSize <= 0: discard await recvFull(client, 2, client.timeout, false) # Skip \c\L break - discard await recvFull(client, chunkSize, client.timeout, true) - discard await recvFull(client, 2, client.timeout, false) # Skip \c\L + var bytesRead = await recvFull(client, chunkSize, client.timeout, true) + if bytesRead != chunkSize: + httpError("Server terminated connection prematurely") + + bytesRead = await recvFull(client, 2, client.timeout, false) # Skip \c\L + if bytesRead != 2: + httpError("Server terminated connection prematurely") + # Trailer headers will only be sent if the request specifies that we want # them: http://tools.ietf.org/html/rfc2616#section-3.6.1 @@ -965,7 +971,7 @@ proc parseBody(client: HttpClient | AsyncHttpClient, if headers.getOrDefault"Connection" == "close" or httpVersion == "1.0": while true: let recvLen = await client.recvFull(4000, client.timeout, true) - if recvLen == 0: + if recvLen != 4000: client.close() break diff --git a/lib/pure/ioselects/ioselectors_epoll.nim b/lib/pure/ioselects/ioselectors_epoll.nim index 144b4ce8df..98b8a2b2bb 100644 --- a/lib/pure/ioselects/ioselectors_epoll.nim +++ b/lib/pure/ioselects/ioselectors_epoll.nim @@ -277,15 +277,16 @@ proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool, var events = {Event.Timer} var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP) epv.data.u64 = fdi.uint + if oneshot: - new_ts.it_interval.tv_sec = 0.Time + new_ts.it_interval.tv_sec = posix.Time(0) new_ts.it_interval.tv_nsec = 0 - new_ts.it_value.tv_sec = (timeout div 1_000).Time + new_ts.it_value.tv_sec = posix.Time(timeout div 1_000) new_ts.it_value.tv_nsec = (timeout %% 1_000) * 1_000_000 incl(events, Event.Oneshot) epv.events = epv.events or EPOLLONESHOT else: - new_ts.it_interval.tv_sec = (timeout div 1000).Time + new_ts.it_interval.tv_sec = posix.Time(timeout div 1000) new_ts.it_interval.tv_nsec = (timeout %% 1_000) * 1_000_000 new_ts.it_value.tv_sec = new_ts.it_interval.tv_sec new_ts.it_value.tv_nsec = new_ts.it_interval.tv_nsec diff --git a/lib/pure/ioselects/ioselectors_kqueue.nim b/lib/pure/ioselects/ioselectors_kqueue.nim index c0b50ab15e..10e23c072b 100644 --- a/lib/pure/ioselects/ioselectors_kqueue.nim +++ b/lib/pure/ioselects/ioselectors_kqueue.nim @@ -452,10 +452,10 @@ proc selectInto*[T](s: Selector[T], timeout: int, if timeout != -1: if timeout >= 1000: - tv.tv_sec = (timeout div 1_000).Time + tv.tv_sec = posix.Time(timeout div 1_000) tv.tv_nsec = (timeout %% 1_000) * 1_000_000 else: - tv.tv_sec = 0.Time + tv.tv_sec = posix.Time(0) tv.tv_nsec = timeout * 1_000_000 else: ptv = nil diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim index 830820fd13..751fc0e8dd 100644 --- a/lib/pure/logging.nim +++ b/lib/pure/logging.nim @@ -107,9 +107,14 @@ var proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): string = ## Format a log message using the ``frmt`` format string, ``level`` and varargs. ## See the module documentation for the format string syntax. + const nilString = "nil" + var msgLen = 0 for arg in args: - msgLen += arg.len + if arg.isNil: + msgLen += nilString.len + else: + msgLen += arg.len result = newStringOfCap(frmt.len + msgLen + 20) var i = 0 while i < frmt.len: @@ -136,7 +141,10 @@ proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): str of "levelname": result.add(LevelNames[level]) else: discard for arg in args: - result.add(arg) + if arg.isNil: + result.add(nilString) + else: + result.add(arg) method log*(logger: Logger, level: Level, args: varargs[string, `$`]) {. raises: [Exception], gcsafe, @@ -361,3 +369,6 @@ when not defined(testing) and isMainModule: addHandler(L) for i in 0 .. 25: info("hello", i) + + var nilString: string + info "hello ", nilString diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 7fd8bbcefd..cbd04a145b 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -291,6 +291,8 @@ when not defined(JS): ## echo fmod(-2.5, 0.3) ## -0.1 else: + proc trunc*(x: float32): float32 {.importc: "Math.trunc", nodecl.} + proc trunc*(x: float64): float64 {.importc: "Math.trunc", nodecl.} proc floor*(x: float32): float32 {.importc: "Math.floor", nodecl.} proc floor*(x: float64): float64 {.importc: "Math.floor", nodecl.} proc ceil*(x: float32): float32 {.importc: "Math.ceil", nodecl.} @@ -349,15 +351,19 @@ proc round*[T: float32|float64](x: T, places: int = 0): T = result = round0(x*mult)/mult when not defined(JS): - proc frexp*(x: float32, exponent: var int): float32 {. + proc c_frexp*(x: float32, exponent: var int32): float32 {. importc: "frexp", header: "".} - proc frexp*(x: float64, exponent: var int): float64 {. + proc c_frexp*(x: float64, exponent: var int32): float64 {. importc: "frexp", header: "".} + proc frexp*[T, U](x: T, exponent: var U): T = ## Split a number into mantissa and exponent. ## `frexp` calculates the mantissa m (a float greater than or equal to 0.5 ## and less than 1) and the integer value n such that `x` (the original ## float value) equals m * 2**n. frexp stores n in `exponent` and returns ## m. + var exp: int32 + result = c_frexp(x, exp) + exponent = exp else: proc frexp*[T: float32|float64](x: T, exponent: var int): T = if x == 0.0: @@ -366,9 +372,14 @@ else: elif x < 0.0: result = -frexp(-x, exponent) else: - var ex = floor(log2(x)) - exponent = round(ex) + var ex = trunc(log2(x)) + exponent = int(ex) result = x / pow(2.0, ex) + if abs(result) >= 1: + inc(exponent) + result = result / 2 + if exponent == 1024 and result == 0.0: + result = 0.99999999999999988898 proc splitDecimal*[T: float32|float64](x: T): tuple[intpart: T, floatpart: T] = ## Breaks `x` into an integral and a fractional part. diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index 9b2d252675..5c73381ffb 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -257,10 +257,13 @@ proc close*(f: var MemFile) = when defined(windows): if f.wasOpened: error = unmapViewOfFile(f.mem) == 0 - lastErr = osLastError() - error = (closeHandle(f.mapHandle) == 0) or error - if f.fHandle != INVALID_HANDLE_VALUE: - error = (closeHandle(f.fHandle) == 0) or error + if not error: + error = closeHandle(f.mapHandle) == 0 + if not error and f.fHandle != INVALID_HANDLE_VALUE: + discard closeHandle(f.fHandle) + f.fHandle = INVALID_HANDLE_VALUE + if error: + lastErr = osLastError() else: error = munmap(f.mem, f.size) != 0 lastErr = osLastError() diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 621ef992cb..e63f7ad55a 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -413,7 +413,7 @@ proc isIpAddress*(address_str: string): bool {.tags: [].} = when defineSsl: CRYPTO_malloc_init() - SslLibraryInit() + doAssert SslLibraryInit() == 1 SslLoadErrorStrings() ErrLoadBioStrings() OpenSSL_add_all_algorithms() @@ -864,6 +864,7 @@ proc close*(socket: Socket) = socket.sslHandle = nil socket.fd.close() + socket.fd = osInvalidSocket when defined(posix): from posix import TCP_NODELAY @@ -1001,15 +1002,25 @@ proc select(readfd: Socket, timeout = 500): int = var fds = @[readfd.fd] result = select(fds, timeout) +proc isClosed(socket: Socket): bool = + socket.fd == osInvalidSocket + +proc uniRecv(socket: Socket, buffer: pointer, size, flags: cint): int = + ## Handles SSL and non-ssl recv in a nice package. + ## + ## In particular handles the case where socket has been closed properly + ## for both SSL and non-ssl. + result = 0 + assert(not socket.isClosed, "Cannot `recv` on a closed socket") + when defineSsl: + if socket.isSsl: + return SSLRead(socket.sslHandle, buffer, size) + + return recv(socket.fd, buffer, size, flags) + proc readIntoBuf(socket: Socket, flags: int32): int = result = 0 - when defineSsl: - if socket.isSSL: - result = SSLRead(socket.sslHandle, addr(socket.buffer), int(socket.buffer.high)) - else: - result = recv(socket.fd, addr(socket.buffer), cint(socket.buffer.high), flags) - else: - result = recv(socket.fd, addr(socket.buffer), cint(socket.buffer.high), flags) + result = uniRecv(socket, addr(socket.buffer), socket.buffer.high, flags) if result < 0: # Save it in case it gets reset (the Nim codegen occasionally may call # Win API functions which reset it). @@ -1055,16 +1066,16 @@ proc recv*(socket: Socket, data: pointer, size: int): int {.tags: [ReadIOEffect] else: when defineSsl: if socket.isSSL: - if socket.sslHasPeekChar: + if socket.sslHasPeekChar: # TODO: Merge this peek char mess into uniRecv copyMem(data, addr(socket.sslPeekChar), 1) socket.sslHasPeekChar = false if size-1 > 0: var d = cast[cstring](data) - result = SSLRead(socket.sslHandle, addr(d[1]), size-1) + 1 + result = uniRecv(socket, addr(d[1]), cint(size-1), 0'i32) + 1 else: result = 1 else: - result = SSLRead(socket.sslHandle, data, size) + result = uniRecv(socket, data, size.cint, 0'i32) else: result = recv(socket.fd, data, size.cint, 0'i32) else: @@ -1131,17 +1142,21 @@ proc recv*(socket: Socket, data: var string, size: int, timeout = -1, ## ## When 0 is returned the socket's connection has been closed. ## - ## This function will throw an EOS exception when an error occurs. A value + ## This function will throw an OSError exception when an error occurs. A value ## lower than 0 is never returned. ## ## A timeout may be specified in milliseconds, if enough data is not received - ## within the time specified an ETimeout exception will be raised. + ## within the time specified an TimeoutError exception will be raised. ## ## **Note**: ``data`` must be initialised. ## ## **Warning**: Only the ``SafeDisconn`` flag is currently supported. data.setLen(size) - result = recv(socket, cstring(data), size, timeout) + result = + if timeout == -1: + recv(socket, cstring(data), size) + else: + recv(socket, cstring(data), size, timeout) if result < 0: data.setLen(0) let lastError = getSocketError(socket) @@ -1178,7 +1193,7 @@ proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} = when defineSsl: if socket.isSSL: if not socket.sslHasPeekChar: - result = SSLRead(socket.sslHandle, addr(socket.sslPeekChar), 1) + result = uniRecv(socket, addr(socket.sslPeekChar), 1, 0'i32) socket.sslHasPeekChar = true c = socket.sslPeekChar @@ -1312,6 +1327,7 @@ proc send*(socket: Socket, data: pointer, size: int): int {. ## ## **Note**: This is a low-level version of ``send``. You likely should use ## the version below. + assert(not socket.isClosed, "Cannot `send` on a closed socket") when defineSsl: if socket.isSSL: return SSLWrite(socket.sslHandle, cast[cstring](data), size) @@ -1356,6 +1372,7 @@ proc sendTo*(socket: Socket, address: string, port: Port, data: pointer, ## which is defined below. ## ## **Note:** This proc is not available for SSL sockets. + assert(not socket.isClosed, "Cannot `sendTo` on a closed socket") var aiList = getAddrInfo(address, port, af) # try all possibilities: diff --git a/lib/pure/oids.nim b/lib/pure/oids.nim index 60b53dbe02..427a68964c 100644 --- a/lib/pure/oids.nim +++ b/lib/pure/oids.nim @@ -88,7 +88,7 @@ proc generatedTime*(oid: Oid): Time = var tmp: int32 var dummy = oid.time bigEndian32(addr(tmp), addr(dummy)) - result = Time(tmp) + result = fromUnix(tmp) when not defined(testing) and isMainModule: let xo = genOid() diff --git a/lib/pure/os.nim b/lib/pure/os.nim index a59134007a..a5db4ed22a 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -173,33 +173,33 @@ proc findExe*(exe: string, followSymlinks: bool = true; return x result = "" -proc getLastModificationTime*(file: string): Time {.rtl, extern: "nos$1".} = +proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1".} = ## Returns the `file`'s last modification time. when defined(posix): var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) - return res.st_mtime + return fromUnix(res.st_mtime.int64) else: var f: WIN32_FIND_DATA var h = findFirstFile(file, f) if h == -1'i32: raiseOSError(osLastError()) - result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime)) + result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftLastWriteTime)).int64) findClose(h) -proc getLastAccessTime*(file: string): Time {.rtl, extern: "nos$1".} = +proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1".} = ## Returns the `file`'s last read or write access time. when defined(posix): var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) - return res.st_atime + return fromUnix(res.st_atime.int64) else: var f: WIN32_FIND_DATA var h = findFirstFile(file, f) if h == -1'i32: raiseOSError(osLastError()) - result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime)) + result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftLastAccessTime)).int64) findClose(h) -proc getCreationTime*(file: string): Time {.rtl, extern: "nos$1".} = +proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1".} = ## Returns the `file`'s creation time. ## ## **Note:** Under POSIX OS's, the returned time may actually be the time at @@ -208,12 +208,12 @@ proc getCreationTime*(file: string): Time {.rtl, extern: "nos$1".} = when defined(posix): var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) - return res.st_ctime + return fromUnix(res.st_ctime.int64) else: var f: WIN32_FIND_DATA var h = findFirstFile(file, f) if h == -1'i32: raiseOSError(osLastError()) - result = winTimeToUnixTime(rdFileTime(f.ftCreationTime)) + result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftCreationTime)).int64) findClose(h) proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} = @@ -672,7 +672,10 @@ template walkCommon(pattern: string, filter) = if dotPos < 0 or idx >= ff.len or ff[idx] == '.' or pattern[dotPos+1] == '*': yield splitFile(pattern).dir / extractFilename(ff) - if findNextFile(res, f) == 0'i32: break + if findNextFile(res, f) == 0'i32: + let errCode = getLastError() + if errCode == ERROR_NO_MORE_FILES: break + else: raiseOSError(errCode.OSErrorCode) else: # here we use glob var f: Glob @@ -782,7 +785,10 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: let xx = if relative: extractFilename(getFilename(f)) else: dir / extractFilename(getFilename(f)) yield (k, xx) - if findNextFile(h, f) == 0'i32: break + if findNextFile(h, f) == 0'i32: + let errCode = getLastError() + if errCode == ERROR_NO_MORE_FILES: break + else: raiseOSError(errCode.OSErrorCode) else: var d = opendir(dir) if d != nil: @@ -816,32 +822,40 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: k = getSymlinkFileKind(y) yield (k, y) -iterator walkDirRec*(dir: string, filter={pcFile, pcDir}): string {. - tags: [ReadDirEffect].} = - ## Recursively walks over the directory `dir` and yields for each file in `dir`. - ## The full path for each file is returned. Directories are not returned. +iterator walkDirRec*(dir: string, yieldFilter = {pcFile}, + followFilter = {pcDir}): string {.tags: [ReadDirEffect].} = + ## Recursively walks over the directory `dir` and yields for each file + ## or directory in `dir`. + ## The full path for each file or directory is returned. ## **Warning**: ## Modifying the directory structure while the iterator ## is traversing may result in undefined behavior! ## - ## Walking is recursive. `filter` controls the behaviour of the iterator: + ## Walking is recursive. `filters` controls the behaviour of the iterator: ## ## --------------------- --------------------------------------------- - ## filter meaning + ## yieldFilter meaning ## --------------------- --------------------------------------------- ## ``pcFile`` yield real files ## ``pcLinkToFile`` yield symbolic links to files + ## ``pcDir`` yield real directories + ## ``pcLinkToDir`` yield symbolic links to directories + ## --------------------- --------------------------------------------- + ## + ## --------------------- --------------------------------------------- + ## followFilter meaning + ## --------------------- --------------------------------------------- ## ``pcDir`` follow real directories ## ``pcLinkToDir`` follow symbolic links to directories ## --------------------- --------------------------------------------- ## var stack = @[dir] while stack.len > 0: - for k,p in walkDir(stack.pop()): - if k in filter: - case k - of pcFile, pcLinkToFile: yield p - of pcDir, pcLinkToDir: stack.add(p) + for k, p in walkDir(stack.pop()): + if k in {pcDir, pcLinkToDir} and k in followFilter: + stack.add(p) + if k in yieldFilter: + yield p proc rawRemoveDir(dir: string) = when defined(windows): @@ -1443,7 +1457,7 @@ proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect].} = winlean.sleep(int32(milsecs)) else: var a, b: Timespec - a.tv_sec = Time(milsecs div 1000) + a.tv_sec = posix.Time(milsecs div 1000) a.tv_nsec = (milsecs mod 1000) * 1000 * 1000 discard posix.nanosleep(a, b) @@ -1481,16 +1495,17 @@ type size*: BiggestInt # Size of file. permissions*: set[FilePermission] # File permissions linkCount*: BiggestInt # Number of hard links the file object has. - lastAccessTime*: Time # Time file was last accessed. - lastWriteTime*: Time # Time file was last modified/written to. - creationTime*: Time # Time file was created. Not supported on all systems! + lastAccessTime*: times.Time # Time file was last accessed. + lastWriteTime*: times.Time # Time file was last modified/written to. + creationTime*: times.Time # Time file was created. Not supported on all systems! template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = ## Transforms the native file info structure into the one nim uses. ## 'rawInfo' is either a 'TBY_HANDLE_FILE_INFORMATION' structure on Windows, ## or a 'Stat' structure on posix when defined(Windows): - template toTime(e: FILETIME): untyped {.gensym.} = winTimeToUnixTime(rdFileTime(e)) # local templates default to bind semantics + template toTime(e: FILETIME): untyped {.gensym.} = + fromUnix(winTimeToUnixTime(rdFileTime(e)).int64) # local templates default to bind semantics template merge(a, b): untyped = a or (b shl 32) formalInfo.id.device = rawInfo.dwVolumeSerialNumber formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh) @@ -1522,9 +1537,9 @@ template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino) formalInfo.size = rawInfo.st_size formalInfo.linkCount = rawInfo.st_Nlink.BiggestInt - formalInfo.lastAccessTime = rawInfo.st_atime - formalInfo.lastWriteTime = rawInfo.st_mtime - formalInfo.creationTime = rawInfo.st_ctime + formalInfo.lastAccessTime = fromUnix(rawInfo.st_atime.int64) + formalInfo.lastWriteTime = fromUnix(rawInfo.st_mtime.int64) + formalInfo.creationTime = fromUnix(rawInfo.st_ctime.int64) result.permissions = {} checkAndIncludeMode(S_IRUSR, fpUserRead) diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 2a1ce0c58b..1625845d18 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -47,6 +47,7 @@ type ProcessObj = object of RootObj when defined(windows): fProcessHandle: Handle + fThreadHandle: Handle inHandle, outHandle, errHandle: FileHandle id: Handle else: @@ -54,6 +55,7 @@ type inStream, outStream, errStream: Stream id: Pid exitStatus: cint + exitFlag: bool options: set[ProcessOption] Process* = ref ProcessObj ## represents an operating system process @@ -237,11 +239,13 @@ proc execProcesses*(cmds: openArray[string], if n > 1: var i = 0 var q = newSeq[Process](n) - var m = min(n, cmds.len) when defined(windows): var w: WOHandleArray + var m = min(min(n, MAXIMUM_WAIT_OBJECTS), cmds.len) var wcount = m + else: + var m = min(n, cmds.len) while i < m: if beforeRunEvent != nil: @@ -253,6 +257,7 @@ proc execProcesses*(cmds: openArray[string], var ecount = len(cmds) while ecount > 0: + var rexit = -1 when defined(windows): # waiting for all children, get result if any child exits var ret = waitForMultipleObjects(int32(wcount), addr(w), 0'i32, @@ -262,22 +267,37 @@ proc execProcesses*(cmds: openArray[string], discard elif ret == WAIT_FAILED: raiseOSError(osLastError()) + else: + var status: int32 + for r in 0..m-1: + if not isNil(q[r]) and q[r].fProcessHandle == w[ret]: + discard getExitCodeProcess(q[r].fProcessHandle, status) + q[r].exitFlag = true + q[r].exitStatus = status + rexit = r + break else: - var status : cint = 1 + var status: cint = 1 # waiting for all children, get result if any child exits let res = waitpid(-1, status, 0) if res > 0: for r in 0..m-1: if not isNil(q[r]) and q[r].id == res: - # we updating `exitStatus` manually, so `running()` can work. if WIFEXITED(status) or WIFSIGNALED(status): + q[r].exitFlag = true q[r].exitStatus = status + rexit = r break else: let err = osLastError() if err == OSErrorCode(ECHILD): # some child exits, we need to check our childs exit codes - discard + for r in 0..m-1: + if (not isNil(q[r])) and (not running(q[r])): + q[r].exitFlag = true + q[r].exitStatus = status + rexit = r + break elif err == OSErrorCode(EINTR): # signal interrupted our syscall, lets repeat it continue @@ -285,26 +305,27 @@ proc execProcesses*(cmds: openArray[string], # all other errors are exceptions raiseOSError(err) - for r in 0..m-1: - if not isNil(q[r]): - if not running(q[r]): - result = max(result, q[r].peekExitCode()) - if afterRunEvent != nil: afterRunEvent(r, q[r]) - close(q[r]) - if i < len(cmds): - if beforeRunEvent != nil: beforeRunEvent(i) - q[r] = startProcess(cmds[i], + if rexit >= 0: + result = max(result, q[rexit].peekExitCode()) + if afterRunEvent != nil: afterRunEvent(rexit, q[rexit]) + close(q[rexit]) + if i < len(cmds): + if beforeRunEvent != nil: beforeRunEvent(i) + q[rexit] = startProcess(cmds[i], options = options + {poEvalCommand}) - when defined(windows): - w[r] = q[r].fProcessHandle - inc(i) - else: - q[r] = nil - when defined(windows): - for c in r..MAXIMUM_WAIT_OBJECTS - 2: - w[c] = w[c + 1] + when defined(windows): + w[rexit] = q[rexit].fProcessHandle + inc(i) + else: + when defined(windows): + for k in 0..wcount - 1: + if w[k] == q[rexit].fProcessHandle: + w[k] = w[wcount - 1] + w[wcount - 1] = 0 dec(wcount) - dec(ecount) + break + q[rexit] = nil + dec(ecount) else: for i in 0..high(cmds): if beforeRunEvent != nil: @@ -491,6 +512,7 @@ when defined(Windows) and not defined(useNimRtl): hi, ho, he: Handle new(result) result.options = options + result.exitFlag = true si.cb = sizeof(si).cint if poParentStreams notin options: si.dwFlags = STARTF_USESTDHANDLES # STARTF_USESHOWWINDOW or @@ -559,28 +581,31 @@ when defined(Windows) and not defined(useNimRtl): "Requested command not found: '$1'. OS error:" % command) else: raiseOSError(lastError, command) - # Close the handle now so anyone waiting is woken: - discard closeHandle(procInfo.hThread) result.fProcessHandle = procInfo.hProcess + result.fThreadHandle = procInfo.hThread result.id = procInfo.dwProcessId + result.exitFlag = false proc close(p: Process) = - if poInteractive in p.options: - # somehow this is not always required on Windows: + if poParentStreams notin p.options: discard closeHandle(p.inHandle) discard closeHandle(p.outHandle) discard closeHandle(p.errHandle) - #discard closeHandle(p.FProcessHandle) + discard closeHandle(p.fThreadHandle) + discard closeHandle(p.fProcessHandle) proc suspend(p: Process) = - discard suspendThread(p.fProcessHandle) + discard suspendThread(p.fThreadHandle) proc resume(p: Process) = - discard resumeThread(p.fProcessHandle) + discard resumeThread(p.fThreadHandle) proc running(p: Process): bool = - var x = waitForSingleObject(p.fProcessHandle, 50) - return x == WAIT_TIMEOUT + if p.exitFlag: + return false + else: + var x = waitForSingleObject(p.fProcessHandle, 0) + return x == WAIT_TIMEOUT proc terminate(p: Process) = if running(p): @@ -590,22 +615,37 @@ when defined(Windows) and not defined(useNimRtl): terminate(p) proc waitForExit(p: Process, timeout: int = -1): int = - discard waitForSingleObject(p.fProcessHandle, timeout.int32) + if p.exitFlag: + return p.exitStatus - var res: int32 - discard getExitCodeProcess(p.fProcessHandle, res) - result = res - p.exitStatus = res - discard closeHandle(p.fProcessHandle) + let res = waitForSingleObject(p.fProcessHandle, timeout.int32) + if res == WAIT_TIMEOUT: + terminate(p) + var status: int32 + discard getExitCodeProcess(p.fProcessHandle, status) + if status != STILL_ACTIVE: + p.exitFlag = true + p.exitStatus = status + discard closeHandle(p.fThreadHandle) + discard closeHandle(p.fProcessHandle) + result = status + else: + result = -1 proc peekExitCode(p: Process): int = - var b = waitForSingleObject(p.fProcessHandle, 50) == WAIT_TIMEOUT - if b: result = -1 - else: - var res: int32 - discard getExitCodeProcess(p.fProcessHandle, res) - if res == 0: return p.exitStatus - return res + if p.exitFlag: + return p.exitStatus + + result = -1 + var b = waitForSingleObject(p.fProcessHandle, 0) == WAIT_TIMEOUT + if not b: + var status: int32 + discard getExitCodeProcess(p.fProcessHandle, status) + p.exitFlag = true + p.exitStatus = status + discard closeHandle(p.fThreadHandle) + discard closeHandle(p.fProcessHandle) + result = status proc inputStream(p: Process): Stream = streamAccess(p) @@ -737,7 +777,8 @@ elif not defined(useNimRtl): pStdin, pStdout, pStderr: array[0..1, cint] new(result) result.options = options - result.exitStatus = -3 # for ``waitForExit`` + result.exitFlag = true + if poParentStreams notin options: if pipe(pStdin) != 0'i32 or pipe(pStdout) != 0'i32 or pipe(pStderr) != 0'i32: @@ -792,6 +833,7 @@ elif not defined(useNimRtl): if poEchoCmd in options: echo(command, " ", join(args, " ")) result.id = pid + result.exitFlag = false if poParentStreams in options: # does not make much sense, but better than nothing: @@ -968,14 +1010,14 @@ elif not defined(useNimRtl): if kill(p.id, SIGCONT) != 0'i32: raiseOsError(osLastError()) proc running(p: Process): bool = - if p.exitStatus != -3: + if p.exitFlag: return false else: - var ret : int - var status : cint = 1 - ret = waitpid(p.id, status, WNOHANG) + var status: cint = 1 + let ret = waitpid(p.id, status, WNOHANG) if ret == int(p.id): if isExitStatus(status): + p.exitFlag = true p.exitStatus = status return false else: @@ -998,13 +1040,14 @@ elif not defined(useNimRtl): import kqueue, times proc waitForExit(p: Process, timeout: int = -1): int = - if p.exitStatus != -3: + if p.exitFlag: return exitStatus(p.exitStatus) if timeout == -1: - var status : cint = 1 + var status: cint = 1 if waitpid(p.id, status, 0) < 0: raiseOSError(osLastError()) + p.exitFlag = true p.exitStatus = status else: var kqFD = kqueue() @@ -1017,15 +1060,15 @@ elif not defined(useNimRtl): var tmspec: Timespec if timeout >= 1000: - tmspec.tv_sec = (timeout div 1_000).Time + tmspec.tv_sec = posix.Time(timeout div 1_000) tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000 else: - tmspec.tv_sec = 0.Time + tmspec.tv_sec = posix.Time(0) tmspec.tv_nsec = (timeout * 1_000_000) try: while true: - var status : cint = 1 + var status: cint = 1 var count = kevent(kqFD, addr(kevIn), 1, addr(kevOut), 1, addr(tmspec)) if count < 0: @@ -1038,12 +1081,14 @@ elif not defined(useNimRtl): raiseOSError(osLastError()) if waitpid(p.id, status, 0) < 0: raiseOSError(osLastError()) + p.exitFlag = true p.exitStatus = status break else: if kevOut.ident == p.id.uint and kevOut.filter == EVFILT_PROC: if waitpid(p.id, status, 0) < 0: raiseOSError(osLastError()) + p.exitFlag = true p.exitStatus = status break else: @@ -1064,36 +1109,33 @@ elif not defined(useNimRtl): var b: Timespec b.tv_sec = e.tv_sec b.tv_nsec = e.tv_nsec - e.tv_sec = (e.tv_sec - s.tv_sec).Time + e.tv_sec = e.tv_sec - s.tv_sec if e.tv_nsec >= s.tv_nsec: e.tv_nsec -= s.tv_nsec else: - if e.tv_sec == 0.Time: + if e.tv_sec == posix.Time(0): raise newException(ValueError, "System time was modified") else: diff = s.tv_nsec - e.tv_nsec e.tv_nsec = 1_000_000_000 - diff - t.tv_sec = (t.tv_sec - e.tv_sec).Time + t.tv_sec = t.tv_sec - e.tv_sec if t.tv_nsec >= e.tv_nsec: t.tv_nsec -= e.tv_nsec else: - t.tv_sec = (int(t.tv_sec) - 1).Time + t.tv_sec = t.tv_sec - posix.Time(1) diff = e.tv_nsec - t.tv_nsec t.tv_nsec = 1_000_000_000 - diff s.tv_sec = b.tv_sec s.tv_nsec = b.tv_nsec - #if waitPid(p.id, p.exitStatus, 0) == int(p.id): - # ``waitPid`` fails if the process is not running anymore. But then - # ``running`` probably set ``p.exitStatus`` for us. Since ``p.exitStatus`` is - # initialized with -3, wrong success exit codes are prevented. - if p.exitStatus != -3: + if p.exitFlag: return exitStatus(p.exitStatus) if timeout == -1: - var status : cint = 1 + var status: cint = 1 if waitpid(p.id, status, 0) < 0: raiseOSError(osLastError()) + p.exitFlag = true p.exitStatus = status else: var nmask, omask: Sigset @@ -1112,10 +1154,10 @@ elif not defined(useNimRtl): raiseOSError(osLastError()) if timeout >= 1000: - tmspec.tv_sec = (timeout div 1_000).Time + tmspec.tv_sec = posix.Time(timeout div 1_000) tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000 else: - tmspec.tv_sec = 0.Time + tmspec.tv_sec = posix.Time(0) tmspec.tv_nsec = (timeout * 1_000_000) try: @@ -1125,9 +1167,10 @@ elif not defined(useNimRtl): let res = sigtimedwait(nmask, sinfo, tmspec) if res == SIGCHLD: if sinfo.si_pid == p.id: - var status : cint = 1 + var status: cint = 1 if waitpid(p.id, status, 0) < 0: raiseOSError(osLastError()) + p.exitFlag = true p.exitStatus = status break else: @@ -1148,9 +1191,10 @@ elif not defined(useNimRtl): # timeout expired, so we trying to kill process if posix.kill(p.id, SIGKILL) == -1: raiseOSError(osLastError()) - var status : cint = 1 + var status: cint = 1 if waitpid(p.id, status, 0) < 0: raiseOSError(osLastError()) + p.exitFlag = true p.exitStatus = status break else: @@ -1168,12 +1212,13 @@ elif not defined(useNimRtl): proc peekExitCode(p: Process): int = var status = cint(0) result = -1 - if p.exitStatus != -3: + if p.exitFlag: return exitStatus(p.exitStatus) var ret = waitpid(p.id, status, WNOHANG) if ret > 0: if isExitStatus(status): + p.exitFlag = true p.exitStatus = status result = exitStatus(status) diff --git a/lib/pure/parsecsv.nim b/lib/pure/parsecsv.nim index ca0f3f9e0d..071858b7c8 100644 --- a/lib/pure/parsecsv.nim +++ b/lib/pure/parsecsv.nim @@ -32,7 +32,7 @@ ## import parsecsv ## import os ## # Prepare a file -## var csv_content = """One,Two,Three,Four +## var content = """One,Two,Three,Four ## 1,2,3,4 ## 10,20,30,40 ## 100,200,300,400 diff --git a/lib/pure/parsesql.nim b/lib/pure/parsesql.nim index 6891e2ff74..ae192ab9a0 100644 --- a/lib/pure/parsesql.nim +++ b/lib/pure/parsesql.nim @@ -55,6 +55,13 @@ const ";", ":", ",", "(", ")", "[", "]", "." ] + reservedKeywords = @[ + # statements + "select", "from", "where", "group", "limit", "having", + # functions + "count", + ] + proc open(L: var SqlLexer, input: Stream, filename: string) = lexbase.open(L, input) L.filename = filename @@ -274,16 +281,16 @@ proc getSymbol(c: var SqlLexer, tok: var Token) = c.bufpos = pos tok.kind = tkIdentifier -proc getQuotedIdentifier(c: var SqlLexer, tok: var Token) = +proc getQuotedIdentifier(c: var SqlLexer, tok: var Token, quote='\"') = var pos = c.bufpos + 1 var buf = c.buf tok.kind = tkQuotedIdentifier while true: var ch = buf[pos] - if ch == '\"': - if buf[pos+1] == '\"': + if ch == quote: + if buf[pos+1] == quote: inc(pos, 2) - add(tok.literal, '\"') + add(tok.literal, quote) else: inc(pos) break @@ -442,7 +449,8 @@ proc getTok(c: var SqlLexer, tok: var Token) = add(tok.literal, '.') of '0'..'9': getNumeric(c, tok) of '\'': getString(c, tok, tkStringConstant) - of '"': getQuotedIdentifier(c, tok) + of '"': getQuotedIdentifier(c, tok, '"') + of '`': getQuotedIdentifier(c, tok, '`') of lexbase.EndOfFile: tok.kind = tkEof tok.literal = "[EOF]" @@ -450,7 +458,7 @@ proc getTok(c: var SqlLexer, tok: var Token) = '\128'..'\255': getSymbol(c, tok) of '+', '-', '*', '/', '<', '>', '=', '~', '!', '@', '#', '%', - '^', '&', '|', '`', '?': + '^', '&', '|', '?': getOperator(c, tok) else: add(tok.literal, c.buf[c.bufpos]) @@ -462,27 +470,27 @@ proc errorStr(L: SqlLexer, msg: string): string = # ----------------------------- parser ---------------------------------------- -# Operator/Element Associativity Description -# . left table/column name separator -# :: left PostgreSQL-style typecast -# [ ] left array element selection -# - right unary minus -# ^ left exponentiation -# * / % left multiplication, division, modulo -# + - left addition, subtraction -# IS IS TRUE, IS FALSE, IS UNKNOWN, IS NULL -# ISNULL test for null -# NOTNULL test for not null -# (any other) left all other native and user-defined oprs -# IN set membership -# BETWEEN range containment -# OVERLAPS time interval overlap -# LIKE ILIKE SIMILAR string pattern matching -# < > less than, greater than -# = right equality, assignment -# NOT right logical negation -# AND left logical conjunction -# OR left logical disjunction +# Operator/Element Associativity Description +# . left table/column name separator +# :: left PostgreSQL-style typecast +# [ ] left array element selection +# - right unary minus +# ^ left exponentiation +# * / % left multiplication, division, modulo +# + - left addition, subtraction +# IS IS TRUE, IS FALSE, IS UNKNOWN, IS NULL +# ISNULL test for null +# NOTNULL test for not null +# (any other) left all other native and user-defined oprs +# IN set membership +# BETWEEN range containment +# OVERLAPS time interval overlap +# LIKE ILIKE SIMILAR string pattern matching +# < > less than, greater than +# = right equality, assignment +# NOT right logical negation +# AND left logical conjunction +# OR left logical disjunction type SqlNodeKind* = enum ## kind of SQL abstract syntax tree @@ -504,6 +512,7 @@ type nkPrefix, nkInfix, nkCall, + nkPrGroup, nkColumnReference, nkReferences, nkDefault, @@ -518,11 +527,15 @@ type nkSelect, nkSelectDistinct, nkSelectColumns, + nkSelectPair, nkAsgn, nkFrom, + nkFromItemPair, nkGroup, + nkLimit, nkHaving, nkOrder, + nkJoin, nkDesc, nkUnion, nkIntersect, @@ -658,10 +671,12 @@ proc getPrecedence(p: SqlParser): int = elif isOpr(p, "=") or isOpr(p, "<") or isOpr(p, ">") or isOpr(p, ">=") or isOpr(p, "<=") or isOpr(p, "<>") or isOpr(p, "!=") or isKeyw(p, "is") or isKeyw(p, "like"): - result = 3 + result = 4 elif isKeyw(p, "and"): - result = 2 + result = 3 elif isKeyw(p, "or"): + result = 2 + elif isKeyw(p, "between"): result = 1 elif p.tok.kind == tkOperator: # user-defined operator: @@ -670,6 +685,7 @@ proc getPrecedence(p: SqlParser): int = result = - 1 proc parseExpr(p: var SqlParser): SqlNode +proc parseSelect(p: var SqlParser): SqlNode proc identOrLiteral(p: var SqlParser): SqlNode = case p.tok.kind @@ -693,7 +709,8 @@ proc identOrLiteral(p: var SqlParser): SqlNode = getTok(p) of tkParLe: getTok(p) - result = parseExpr(p) + result = newNode(nkPrGroup) + result.add(parseExpr(p)) eat(p, tkParRi) else: sqlError(p, "expression expected") @@ -745,7 +762,7 @@ proc lowestExprAux(p: var SqlParser, v: var SqlNode, limit: int): int = result = opPred while opPred > limit: node = newNode(nkInfix) - opNode = newNode(nkIdent, p.tok.literal) + opNode = newNode(nkIdent, p.tok.literal.toLower()) getTok(p) result = lowestExprAux(p, v2, opPred) node.add(opNode) @@ -921,6 +938,19 @@ proc parseWhere(p: var SqlParser): SqlNode = result = newNode(nkWhere) result.add(parseExpr(p)) +proc parseFromItem(p: var SqlParser): SqlNode = + result = newNode(nkFromItemPair) + if p.tok.kind == tkParLe: + getTok(p) + var select = parseSelect(p) + result.add(select) + eat(p, tkParRi) + else: + result.add(parseExpr(p)) + if isKeyw(p, "as"): + getTok(p) + result.add(parseExpr(p)) + proc parseIndexDef(p: var SqlParser): SqlNode = result = parseIfNotExists(p, nkCreateIndex) if isKeyw(p, "primary"): @@ -997,6 +1027,8 @@ proc parseUpdate(p: var SqlParser): SqlNode = proc parseDelete(p: var SqlParser): SqlNode = getTok(p) + if isOpr(p, "*"): + getTok(p) result = newNode(nkDelete) eat(p, "from") result.add(primary(p)) @@ -1019,7 +1051,12 @@ proc parseSelect(p: var SqlParser): SqlNode = a.add(newNode(nkIdent, "*")) getTok(p) else: - a.add(parseExpr(p)) + var pair = newNode(nkSelectPair) + pair.add(parseExpr(p)) + a.add(pair) + if isKeyw(p, "as"): + getTok(p) + pair.add(parseExpr(p)) if p.tok.kind != tkComma: break getTok(p) result.add(a) @@ -1027,7 +1064,7 @@ proc parseSelect(p: var SqlParser): SqlNode = var f = newNode(nkFrom) while true: getTok(p) - f.add(parseExpr(p)) + f.add(parseFromItem(p)) if p.tok.kind != tkComma: break result.add(f) if isKeyw(p, "where"): @@ -1041,6 +1078,11 @@ proc parseSelect(p: var SqlParser): SqlNode = if p.tok.kind != tkComma: break getTok(p) result.add(g) + if isKeyw(p, "limit"): + getTok(p) + var l = newNode(nkLimit) + l.add(parseExpr(p)) + result.add(l) if isKeyw(p, "having"): var h = newNode(nkHaving) while true: @@ -1073,6 +1115,19 @@ proc parseSelect(p: var SqlParser): SqlNode = if p.tok.kind != tkComma: break getTok(p) result.add(n) + if isKeyw(p, "join") or isKeyw(p, "inner") or isKeyw(p, "outer") or isKeyw(p, "cross"): + var join = newNode(nkJoin) + result.add(join) + if isKeyw(p, "join"): + join.add(newNode(nkIdent, "")) + getTok(p) + else: + join.add(newNode(nkIdent, p.tok.literal.toLower())) + getTok(p) + eat(p, "join") + join.add(parseFromItem(p)) + eat(p, "on") + join.add(parseExpr(p)) proc parseStmt(p: var SqlParser; parent: SqlNode) = if isKeyw(p, "create"): @@ -1104,7 +1159,7 @@ proc parseStmt(p: var SqlParser; parent: SqlNode) = elif isKeyw(p, "begin"): getTok(p) else: - sqlError(p, "CREATE expected") + sqlError(p, "SELECT, CREATE, UPDATE or DELETE expected") proc open(p: var SqlParser, input: Stream, filename: string) = ## opens the parser `p` and assigns the input stream `input` to it. @@ -1116,13 +1171,13 @@ proc open(p: var SqlParser, input: Stream, filename: string) = proc parse(p: var SqlParser): SqlNode = ## parses the content of `p`'s input stream and returns the SQL AST. - ## Syntax errors raise an `EInvalidSql` exception. + ## Syntax errors raise an `SqlParseError` exception. result = newNode(nkStmtList) while p.tok.kind != tkEof: parseStmt(p, result) + if p.tok.kind == tkEof: + break eat(p, tkSemicolon) - if result.len == 1: - result = result.sons[0] proc close(p: var SqlParser) = ## closes the parser `p`. The associated input stream is closed too. @@ -1131,7 +1186,7 @@ proc close(p: var SqlParser) = proc parseSQL*(input: Stream, filename: string): SqlNode = ## parses the SQL from `input` into an AST and returns the AST. ## `filename` is only used for error messages. - ## Syntax errors raise an `EInvalidSql` exception. + ## Syntax errors raise an `SqlParseError` exception. var p: SqlParser open(p, input, filename) try: @@ -1139,24 +1194,69 @@ proc parseSQL*(input: Stream, filename: string): SqlNode = finally: close(p) -proc ra(n: SqlNode, s: var string, indent: int) +proc parseSQL*(input: string, filename=""): SqlNode = + ## parses the SQL from `input` into an AST and returns the AST. + ## `filename` is only used for error messages. + ## Syntax errors raise an `SqlParseError` exception. + parseSQL(newStringStream(input), "") -proc rs(n: SqlNode, s: var string, indent: int, - prefix = "(", suffix = ")", - sep = ", ") = + +type + SqlWriter = object + indent: int + upperCase: bool + buffer: string + +proc add(s: var SqlWriter, thing: char) = + s.buffer.add(thing) + +proc add(s: var SqlWriter, thing: string) = + if s.buffer.len > 0 and s.buffer[^1] notin {' ', '\L', '(', '.'}: + s.buffer.add(" ") + s.buffer.add(thing) + +proc addKeyw(s: var SqlWriter, thing: string) = + var keyw = thing + if s.upperCase: + keyw = keyw.toUpper() + s.add(keyw) + +proc addIden(s: var SqlWriter, thing: string) = + var iden = thing + if iden.toLower() in reservedKeywords: + iden = '"' & iden & '"' + s.add(iden) + +proc ra(n: SqlNode, s: var SqlWriter) + +proc rs(n: SqlNode, s: var SqlWriter, prefix = "(", suffix = ")", sep = ", ") = if n.len > 0: s.add(prefix) for i in 0 .. n.len-1: if i > 0: s.add(sep) - ra(n.sons[i], s, indent) + ra(n.sons[i], s) s.add(suffix) -proc ra(n: SqlNode, s: var string, indent: int) = +proc addMulti(s: var SqlWriter, n: SqlNode, sep = ',') = + if n.len > 0: + for i in 0 .. n.len-1: + if i > 0: s.add(sep) + ra(n.sons[i], s) + +proc addMulti(s: var SqlWriter, n: SqlNode, sep = ',', prefix, suffix: char) = + if n.len > 0: + s.add(prefix) + for i in 0 .. n.len-1: + if i > 0: s.add(sep) + ra(n.sons[i], s) + s.add(suffix) + +proc ra(n: SqlNode, s: var SqlWriter) = if n == nil: return case n.kind of nkNone: discard of nkIdent: - if allCharsInSet(n.strVal, {'\33'..'\127'}): + if allCharsInSet(n.strVal, {'\33'..'\127'}) and n.strVal.toLower() notin reservedKeywords: s.add(n.strVal) else: s.add("\"" & replace(n.strVal, "\"", "\"\"") & "\"") @@ -1169,217 +1269,206 @@ proc ra(n: SqlNode, s: var string, indent: int) = of nkIntegerLit, nkNumericLit: s.add(n.strVal) of nkPrimaryKey: - s.add(" primary key") - rs(n, s, indent) + s.addKeyw("primary key") + rs(n, s) of nkForeignKey: - s.add(" foreign key") - rs(n, s, indent) + s.addKeyw("foreign key") + rs(n, s) of nkNotNull: - s.add(" not null") + s.addKeyw("not null") of nkNull: - s.add(" null") + s.addKeyw("null") of nkDot: - ra(n.sons[0], s, indent) - s.add(".") - ra(n.sons[1], s, indent) + ra(n.sons[0], s) + s.add('.') + ra(n.sons[1], s) of nkDotDot: - ra(n.sons[0], s, indent) + ra(n.sons[0], s) s.add(". .") - ra(n.sons[1], s, indent) + ra(n.sons[1], s) of nkPrefix: - s.add('(') - ra(n.sons[0], s, indent) + ra(n.sons[0], s) s.add(' ') - ra(n.sons[1], s, indent) - s.add(')') + ra(n.sons[1], s) of nkInfix: - s.add('(') - ra(n.sons[1], s, indent) + ra(n.sons[1], s) s.add(' ') - ra(n.sons[0], s, indent) + ra(n.sons[0], s) s.add(' ') - ra(n.sons[2], s, indent) - s.add(')') + ra(n.sons[2], s) of nkCall, nkColumnReference: - ra(n.sons[0], s, indent) + ra(n.sons[0], s) s.add('(') for i in 1..n.len-1: - if i > 1: s.add(", ") - ra(n.sons[i], s, indent) + if i > 1: s.add(',') + ra(n.sons[i], s) + s.add(')') + of nkPrGroup: + s.add('(') + s.addMulti(n) s.add(')') of nkReferences: - s.add(" references ") - ra(n.sons[0], s, indent) + s.addKeyw("references") + ra(n.sons[0], s) of nkDefault: - s.add(" default ") - ra(n.sons[0], s, indent) + s.addKeyw("default") + ra(n.sons[0], s) of nkCheck: - s.add(" check ") - ra(n.sons[0], s, indent) + s.addKeyw("check") + ra(n.sons[0], s) of nkConstraint: - s.add(" constraint ") - ra(n.sons[0], s, indent) - s.add(" check ") - ra(n.sons[1], s, indent) + s.addKeyw("constraint") + ra(n.sons[0], s) + s.addKeyw("check") + ra(n.sons[1], s) of nkUnique: - s.add(" unique") - rs(n, s, indent) + s.addKeyw("unique") + rs(n, s) of nkIdentity: - s.add(" identity") + s.addKeyw("identity") of nkColumnDef: - s.add("\n ") - rs(n, s, indent, "", "", " ") + rs(n, s, "", "", " ") of nkStmtList: for i in 0..n.len-1: - ra(n.sons[i], s, indent) - s.add("\n") + ra(n.sons[i], s) + s.add(';') of nkInsert: assert n.len == 3 - s.add("insert into ") - ra(n.sons[0], s, indent) - ra(n.sons[1], s, indent) + s.addKeyw("insert into") + ra(n.sons[0], s) + s.add(' ') + ra(n.sons[1], s) if n.sons[2].kind == nkDefault: - s.add("default values") + s.addKeyw("default values") else: - s.add("\n") - ra(n.sons[2], s, indent) - s.add(';') + ra(n.sons[2], s) of nkUpdate: - s.add("update ") - ra(n.sons[0], s, indent) - s.add(" set ") + s.addKeyw("update") + ra(n.sons[0], s) + s.addKeyw("set") var L = n.len for i in 1 .. L-2: if i > 1: s.add(", ") var it = n.sons[i] assert it.kind == nkAsgn - ra(it, s, indent) - ra(n.sons[L-1], s, indent) - s.add(';') + ra(it, s) + ra(n.sons[L-1], s) of nkDelete: - s.add("delete from ") - ra(n.sons[0], s, indent) - ra(n.sons[1], s, indent) - s.add(';') + s.addKeyw("delete from") + ra(n.sons[0], s) + ra(n.sons[1], s) of nkSelect, nkSelectDistinct: - s.add("select ") + s.addKeyw("select") if n.kind == nkSelectDistinct: - s.add("distinct ") - rs(n.sons[0], s, indent, "", "", ", ") - for i in 1 .. n.len-1: ra(n.sons[i], s, indent) - s.add(';') + s.addKeyw("distinct") + s.addMulti(n.sons[0]) + for i in 1 .. n.len-1: + ra(n.sons[i], s) of nkSelectColumns: assert(false) + of nkSelectPair: + ra(n.sons[0], s) + if n.sons.len == 2: + s.addKeyw("as") + ra(n.sons[1], s) + of nkFromItemPair: + if n.sons[0].kind == nkIdent: + ra(n.sons[0], s) + else: + assert n.sons[0].kind == nkSelect + s.add('(') + ra(n.sons[0], s) + s.add(')') + if n.sons.len == 2: + s.addKeyw("as") + ra(n.sons[1], s) of nkAsgn: - ra(n.sons[0], s, indent) + ra(n.sons[0], s) s.add(" = ") - ra(n.sons[1], s, indent) + ra(n.sons[1], s) of nkFrom: - s.add("\nfrom ") - rs(n, s, indent, "", "", ", ") + s.addKeyw("from") + s.addMulti(n) of nkGroup: - s.add("\ngroup by") - rs(n, s, indent, "", "", ", ") + s.addKeyw("group by") + s.addMulti(n) + of nkLimit: + s.addKeyw("limit") + s.addMulti(n) of nkHaving: - s.add("\nhaving") - rs(n, s, indent, "", "", ", ") + s.addKeyw("having") + s.addMulti(n) of nkOrder: - s.add("\norder by ") - rs(n, s, indent, "", "", ", ") + s.addKeyw("order by") + s.addMulti(n) + of nkJoin: + var joinType = n.sons[0].strVal + if joinType == "": + joinType = "join" + else: + joinType &= " " & "join" + s.addKeyw(joinType) + ra(n.sons[1], s) + s.addKeyw("on") + ra(n.sons[2], s) of nkDesc: - ra(n.sons[0], s, indent) - s.add(" desc") + ra(n.sons[0], s) + s.addKeyw("desc") of nkUnion: - s.add(" union") + s.addKeyw("union") of nkIntersect: - s.add(" intersect") + s.addKeyw("intersect") of nkExcept: - s.add(" except") + s.addKeyw("except") of nkColumnList: - rs(n, s, indent) + rs(n, s) of nkValueList: - s.add("values ") - rs(n, s, indent) + s.addKeyw("values") + rs(n, s) of nkWhere: - s.add("\nwhere ") - ra(n.sons[0], s, indent) + s.addKeyw("where") + ra(n.sons[0], s) of nkCreateTable, nkCreateTableIfNotExists: - s.add("create table ") + s.addKeyw("create table") if n.kind == nkCreateTableIfNotExists: - s.add("if not exists ") - ra(n.sons[0], s, indent) + s.addKeyw("if not exists") + ra(n.sons[0], s) s.add('(') for i in 1..n.len-1: - if i > 1: s.add(", ") - ra(n.sons[i], s, indent) + if i > 1: s.add(',') + ra(n.sons[i], s) s.add(");") of nkCreateType, nkCreateTypeIfNotExists: - s.add("create type ") + s.addKeyw("create type") if n.kind == nkCreateTypeIfNotExists: - s.add("if not exists ") - ra(n.sons[0], s, indent) - s.add(" as ") - ra(n.sons[1], s, indent) - s.add(';') + s.addKeyw("if not exists") + ra(n.sons[0], s) + s.addKeyw("as") + ra(n.sons[1], s) of nkCreateIndex, nkCreateIndexIfNotExists: - s.add("create index ") + s.addKeyw("create index") if n.kind == nkCreateIndexIfNotExists: - s.add("if not exists ") - ra(n.sons[0], s, indent) - s.add(" on ") - ra(n.sons[1], s, indent) + s.addKeyw("if not exists") + ra(n.sons[0], s) + s.addKeyw("on") + ra(n.sons[1], s) s.add('(') for i in 2..n.len-1: if i > 2: s.add(", ") - ra(n.sons[i], s, indent) + ra(n.sons[i], s) s.add(");") of nkEnumDef: - s.add("enum ") - rs(n, s, indent) + s.addKeyw("enum") + rs(n, s) -# What I want: -# -#select(columns = [T1.all, T2.name], -# fromm = [T1, T2], -# where = T1.name ==. T2.name, -# orderby = [name]): -# -#for row in dbQuery(db, """select x, y, z -# from a, b -# where a.name = b.name"""): -# - -#select x, y, z: -# fromm: Table1, Table2 -# where: x.name == y.name -#db.select(fromm = [t1, t2], where = t1.name == t2.name): -#for x, y, z in db.select(fromm = a, b where = a.name == b.name): -# writeLine x, y, z - -proc renderSQL*(n: SqlNode): string = +proc renderSQL*(n: SqlNode, upperCase=false): string = ## Converts an SQL abstract syntax tree to its string representation. - result = "" - ra(n, result, 0) + var s: SqlWriter + s.buffer = "" + s.upperCase = upperCase + ra(n, s) + return s.buffer proc `$`*(n: SqlNode): string = ## an alias for `renderSQL`. renderSQL(n) - -when not defined(testing) and isMainModule: - echo(renderSQL(parseSQL(newStringStream(""" - CREATE TYPE happiness AS ENUM ('happy', 'very happy', 'ecstatic'); - CREATE TABLE holidays ( - num_weeks int, - happiness happiness - ); - CREATE INDEX table1_attr1 ON table1(attr1); - - SELECT * FROM myTab WHERE col1 = 'happy'; - """), "stdin"))) - -# CREATE TYPE happiness AS ENUM ('happy', 'very happy', 'ecstatic'); -# CREATE TABLE holidays ( -# num_weeks int, -# happiness happiness -# ); -# CREATE INDEX table1_attr1 ON table1(attr1) diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index a602b0e1be..57387e62ed 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -87,6 +87,23 @@ proc parseOct*(s: string, number: var int, start = 0): int {. inc(i) if foundDigit: result = i-start +proc parseBin*(s: string, number: var int, start = 0): int {. + rtl, extern: "npuParseBin", noSideEffect.} = + ## parses an binary number and stores its value in ``number``. Returns + ## the number of the parsed characters or 0 in case of an error. + var i = start + var foundDigit = false + if s[i] == '0' and (s[i+1] == 'b' or s[i+1] == 'B'): inc(i, 2) + while true: + case s[i] + of '_': discard + of '0'..'1': + number = number shl 1 or (ord(s[i]) - ord('0')) + foundDigit = true + else: break + inc(i) + if foundDigit: result = i-start + proc parseIdent*(s: string, ident: var string, start = 0): int = ## parses an identifier and stores it in ``ident``. Returns ## the number of the parsed characters or 0 in case of an error. diff --git a/lib/pure/random.nim b/lib/pure/random.nim index e6a9162c58..de419b9fbf 100644 --- a/lib/pure/random.nim +++ b/lib/pure/random.nim @@ -7,16 +7,16 @@ # distribution, for details about the copyright. # -## Nim's standard random number generator. Based on the ``xoroshiro128+`` (xor/rotate/shift/rotate) library. +## Nim's standard random number generator. Based on +## the ``xoroshiro128+`` (xor/rotate/shift/rotate) library. ## * More information: http://xoroshiro.di.unimi.it/ ## * C implementation: http://xoroshiro.di.unimi.it/xoroshiro128plus.c ## -## Do not use this module for cryptographic use! +## **Do not use this module for cryptographic purposes!** include "system/inclrtl" {.push debugger:off.} -# XXX Expose RandomGenState when defined(JS): type ui = uint32 @@ -27,31 +27,34 @@ else: const randMax = 18_446_744_073_709_551_615u64 type - RandomGenState = object + Rand* = object ## State of the random number generator. + ## The procs that use the default state + ## are **not** thread-safe! a0, a1: ui when defined(JS): - var state = RandomGenState( + var state = Rand( a0: 0x69B4C98Cu32, a1: 0xFED1DD30u32) # global for backwards compatibility else: # racy for multi-threading but good enough for now: - var state = RandomGenState( + var state = Rand( a0: 0x69B4C98CB8530805u64, a1: 0xFED1DD3004688D67CAu64) # global for backwards compatibility proc rotl(x, k: ui): ui = result = (x shl k) or (x shr (ui(64) - k)) -proc next(s: var RandomGenState): uint64 = - let s0 = s.a0 - var s1 = s.a1 +proc next*(r: var Rand): uint64 = + ## Uses the state to compute a new ``uint64`` random number. + let s0 = r.a0 + var s1 = r.a1 result = s0 + s1 s1 = s1 xor s0 - s.a0 = rotl(s0, 55) xor s1 xor (s1 shl 14) # a, b - s.a1 = rotl(s1, 36) # c + r.a0 = rotl(s0, 55) xor s1 xor (s1 shl 14) # a, b + r.a1 = rotl(s1, 36) # c -proc skipRandomNumbers(s: var RandomGenState) = +proc skipRandomNumbers*(s: var Rand) = ## This is the jump function for the generator. It is equivalent ## to 2^64 calls to next(); it can be used to generate 2^64 ## non-overlapping subsequences for parallel computations. @@ -71,21 +74,23 @@ proc skipRandomNumbers(s: var RandomGenState) = s.a0 = s0 s.a1 = s1 -proc random*(max: int): int {.benign.} = +proc random*(max: int): int {.benign, deprecated.} = ## Returns a random number in the range 0..max-1. The sequence of ## random number is always the same, unless `randomize` is called ## which initializes the random number generator with a "random" - ## number, i.e. a tickcount. + ## number, i.e. a tickcount. **Deprecated since version 0.18.0**. + ## Use ``rand`` instead. while true: let x = next(state) if x < randMax - (randMax mod ui(max)): return int(x mod uint64(max)) -proc random*(max: float): float {.benign.} = +proc random*(max: float): float {.benign, deprecated.} = ## Returns a random number in the range 0.. 130: + elif oc > 150: doAssert false, "too many occurrences of " & $i var a = [0, 1] diff --git a/lib/pure/rationals.nim b/lib/pure/rationals.nim index 7fb24c26f3..7907b4e6c0 100644 --- a/lib/pure/rationals.nim +++ b/lib/pure/rationals.nim @@ -39,7 +39,7 @@ proc toRational*[T:SomeInteger](x: T): Rational[T] = result.num = x result.den = 1 -proc toRational*(x: float, n: int = high(int32)): Rational[int] = +proc toRational*(x: float, n: int = high(int) shr (sizeof(int) div 2 * 8)): Rational[int] = ## Calculates the best rational numerator and denominator ## that approximates to `x`, where the denominator is ## smaller than `n` (default is the largest possible @@ -323,8 +323,13 @@ when isMainModule: assert abs(toFloat(y) - 0.4814814814814815) < 1.0e-7 assert toInt(z) == 0 - assert toRational(0.98765432) == 2111111029 // 2137499919 - assert toRational(PI) == 817696623 // 260280919 + when sizeof(int) == 8: + assert toRational(0.98765432) == 2111111029 // 2137499919 + assert toRational(PI) == 817696623 // 260280919 + when sizeof(int) == 4: + assert toRational(0.98765432) == 80 // 81 + assert toRational(PI) == 355 // 113 + assert toRational(0.1) == 1 // 10 assert toRational(0.9) == 9 // 10 diff --git a/lib/pure/strformat.nim b/lib/pure/strformat.nim new file mode 100644 index 0000000000..ba21f894f8 --- /dev/null +++ b/lib/pure/strformat.nim @@ -0,0 +1,638 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +##[ +String `interpolation`:idx: / `format`:idx: inspired by +Python's ``f``-strings. + +Examples: + +.. code-block:: nim + + doAssert &"""{"abc":>4}""" == " abc" + doAssert &"""{"abc":<4}""" == "abc " + + doAssert &"{-12345:08}" == "-0012345" + doAssert &"{-1:3}" == " -1" + doAssert &"{-1:03}" == "-01" + doAssert &"{16:#X}" == "0x10" + + doAssert &"{123.456}" == "123.456" + doAssert &"{123.456:>9.3f}" == " 123.456" + doAssert &"{123.456:9.3f}" == " 123.456" + doAssert &"{123.456:9.4f}" == " 123.4560" + doAssert &"{123.456:>9.0f}" == " 123." + doAssert &"{123.456:<9.4f}" == "123.4560 " + + doAssert &"{123.456:e}" == "1.234560e+02" + doAssert &"{123.456:>13e}" == " 1.234560e+02" + doAssert &"{123.456:13e}" == " 1.234560e+02" + + +An expression like ``&"{key} is {value:arg} {{z}}"`` is transformed into: + +.. code-block:: nim + var temp = newStringOfCap(educatedCapGuess) + format(key, temp) + format(" is ", temp) + format(value, arg, temp) + format(" {z}", temp) + temp + +Parts of the string that are enclosed in the curly braces are interpreted +as Nim code, to escape an ``{`` or ``}`` double it. + +``&`` delegates most of the work to an open overloaded set +of ``format`` procs. The required signature for a type ``T`` that supports +formatting is usually ``proc format(x: T; result: var string)`` for efficiency +but can also be ``proc format(x: T): string``. ``add`` and ``$`` procs are +used as the fallback implementation. + +This is the concrete lookup algorithm that ``&`` uses: + +.. code-block:: nim + + when compiles(format(arg, res)): + format(arg, res) + elif compiles(format(arg)): + res.add format(arg) + elif compiles(add(res, arg)): + res.add(arg) + else: + res.add($arg) + + +The subexpression after the colon +(``arg`` in ``&"{key} is {value:arg} {{z}}"``) is an optional argument +passed to ``format``. + +If an optional argument is present the following lookup algorithm is used: + +.. code-block:: nim + + when compiles(format(arg, option, res)): + format(arg, option, res) + else: + res.add format(arg, option) + + +For strings and numeric types the optional argument is a so-called +"standard format specifier". + + +Standard format specifier +========================= + + +The general form of a standard format specifier is:: + + [[fill]align][sign][#][0][minimumwidth][.precision][type] + +The square brackets ``[]`` indicate an optional element. + +The optional align flag can be one of the following: + +'<' + Forces the field to be left-aligned within the available + space. (This is the default for strings.) + +'>' + Forces the field to be right-aligned within the available space. + (This is the default for numbers.) + +'^' + Forces the field to be centered within the available space. + +Note that unless a minimum field width is defined, the field width +will always be the same size as the data to fill it, so that the alignment +option has no meaning in this case. + +The optional 'fill' character defines the character to be used to pad +the field to the minimum width. The fill character, if present, must be +followed by an alignment flag. + +The 'sign' option is only valid for numeric types, and can be one of the following: + +================= ==================================================== + Sign Meaning +================= ==================================================== +``+`` Indicates that a sign should be used for both + positive as well as negative numbers. +``-`` Indicates that a sign should be used only for + negative numbers (this is the default behavior). +(space) Indicates that a leading space should be used on + positive numbers. +================= ==================================================== + +If the '#' character is present, integers use the 'alternate form' for formatting. +This means that binary, octal, and hexadecimal output will be prefixed +with '0b', '0o', and '0x', respectively. + +'width' is a decimal integer defining the minimum field width. If not specified, +then the field width will be determined by the content. + +If the width field is preceded by a zero ('0') character, this enables +zero-padding. + +The 'precision' is a decimal number indicating how many digits should be displayed +after the decimal point in a floating point conversion. For non-numeric types the +field indicates the maximum field size - in other words, how many characters will +be used from the field content. The precision is ignored for integer conversions. + +Finally, the 'type' determines how the data should be presented. + +The available integer presentation types are: + + +================= ==================================================== + Type Result +================= ==================================================== +``b`` Binary. Outputs the number in base 2. +``d`` Decimal Integer. Outputs the number in base 10. +``o`` Octal format. Outputs the number in base 8. +``x`` Hex format. Outputs the number in base 16, using + lower-case letters for the digits above 9. +``X`` Hex format. Outputs the number in base 16, using + uppercase letters for the digits above 9. +(None) the same as 'd' +================= ==================================================== + + +The available floating point presentation types are: + +================= ==================================================== + Type Result +================= ==================================================== +``e`` Exponent notation. Prints the number in scientific + notation using the letter 'e' to indicate the + exponent. +``E`` Exponent notation. Same as 'e' except it converts + the number to uppercase. +``f`` Fixed point. Displays the number as a fixed-point + number. +``F`` Fixed point. Same as 'f' except it converts the + number to uppercase. +``g`` General format. This prints the number as a + fixed-point number, unless the number is too + large, in which case it switches to 'e' + exponent notation. +``G`` General format. Same as 'g' except switches to 'E' + if the number gets to large. +(None) similar to 'g', except that it prints at least one + digit after the decimal point. +================= ==================================================== + + +Future directions +================= + +A curly expression with commas in it like ``{x, argA, argB}`` could be +transformed to ``format(x, argA, argB, res)`` in order to support +formatters that do not need to parse a custom language within a custom +language but instead prefer to use Nim's existing syntax. This also +helps in readability since there is only so much you can cram into +single letter DSLs. + +]## + +import macros, parseutils, unicode +import strutils + +template callFormat(res, arg) {.dirty.} = + when arg is string: + # workaround in order to circumvent 'strutils.format' which matches + # too but doesn't adhere to our protocol. + res.add arg + elif compiles(format(arg, res)): + format(arg, res) + elif compiles(format(arg)): + res.add format(arg) + elif compiles(add(res, arg)): + res.add(arg) + else: + res.add($arg) + +template callFormatOption(res, arg, option) {.dirty.} = + when compiles(format(arg, option, res)): + format(arg, option, res) + elif compiles(format(arg, option)): + res.add format(arg, option) + else: + format($arg, option, res) + +macro `&`*(pattern: string): untyped = + ## For a specification of the ``&`` macro, see the module level documentation. + runnableExamples: + template check(actual, expected: string) = + doAssert actual == expected + + from strutils import toUpperAscii, repeat + + # Basic tests + let s = "string" + check &"{0} {s}", "0 string" + check &"{s[0..2].toUpperAscii}", "STR" + check &"{-10:04}", "-010" + check &"{-10:<04}", "-010" + check &"{-10:>04}", "-010" + check &"0x{10:02X}", "0x0A" + + check &"{10:#04X}", "0x0A" + + check &"""{"test":#>5}""", "#test" + check &"""{"test":>5}""", " test" + + check &"""{"test":#^7}""", "#test##" + + check &"""{"test": <5}""", "test " + check &"""{"test":<5}""", "test " + check &"{1f:.3f}", "1.000" + check &"Hello, {s}!", "Hello, string!" + + # Tests for identifers without parenthesis + check &"{s} works{s}", "string worksstring" + check &"{s:>7}", " string" + doAssert(not compiles(&"{s_works}")) # parsed as identifier `s_works` + + # Misc general tests + check &"{{}}", "{}" + check &"{0}%", "0%" + check &"{0}%asdf", "0%asdf" + check &("\n{\"\\n\"}\n"), "\n\n\n" + check &"""{"abc"}s""", "abcs" + + # String tests + check &"""{"abc"}""", "abc" + check &"""{"abc":>4}""", " abc" + check &"""{"abc":<4}""", "abc " + check &"""{"":>4}""", " " + check &"""{"":<4}""", " " + + # Int tests + check &"{12345}", "12345" + check &"{ - 12345}", "-12345" + check &"{12345:6}", " 12345" + check &"{12345:>6}", " 12345" + check &"{12345:4}", "12345" + check &"{12345:08}", "00012345" + check &"{-12345:08}", "-0012345" + check &"{0:0}", "0" + check &"{0:02}", "00" + check &"{-1:3}", " -1" + check &"{-1:03}", "-01" + check &"{10}", "10" + check &"{16:#X}", "0x10" + check &"{16:^#7X}", " 0x10 " + check &"{16:^+#7X}", " +0x10 " + + # Hex tests + check &"{0:x}", "0" + check &"{-0:x}", "0" + check &"{255:x}", "ff" + check &"{255:X}", "FF" + check &"{-255:x}", "-ff" + check &"{-255:X}", "-FF" + check &"{255:x} uNaffeCteD CaSe", "ff uNaffeCteD CaSe" + check &"{255:X} uNaffeCteD CaSe", "FF uNaffeCteD CaSe" + check &"{255:4x}", " ff" + check &"{255:04x}", "00ff" + check &"{-255:4x}", " -ff" + check &"{-255:04x}", "-0ff" + + # Float tests + check &"{123.456}", "123.456" + check &"{-123.456}", "-123.456" + check &"{123.456:.3f}", "123.456" + check &"{123.456:+.3f}", "+123.456" + check &"{-123.456:+.3f}", "-123.456" + check &"{-123.456:.3f}", "-123.456" + check &"{123.456:1g}", "123.456" + check &"{123.456:.1f}", "123.5" + check &"{123.456:.0f}", "123." + #check &"{123.456:.0f}", "123." + check &"{123.456:>9.3f}", " 123.456" + check &"{123.456:9.3f}", " 123.456" + check &"{123.456:>9.4f}", " 123.4560" + check &"{123.456:>9.0f}", " 123." + check &"{123.456:<9.4f}", "123.4560 " + + # Float (scientific) tests + check &"{123.456:e}", "1.234560e+02" + check &"{123.456:>13e}", " 1.234560e+02" + check &"{123.456:<13e}", "1.234560e+02 " + check &"{123.456:.1e}", "1.2e+02" + check &"{123.456:.2e}", "1.23e+02" + check &"{123.456:.3e}", "1.235e+02" + + # Note: times.format adheres to the format protocol. Test that this + # works: + import times + + var nullTime: DateTime + check &"{nullTime:yyyy-mm-dd}", "0000-00-00" + + # Unicode string tests + check &"""{"αβγ"}""", "αβγ" + check &"""{"αβγ":>5}""", " αβγ" + check &"""{"αβγ":<5}""", "αβγ " + check &"""a{"a"}α{"α"}€{"€"}𐍈{"𐍈"}""", "aaαα€€𐍈𐍈" + check &"""a{"a":2}α{"α":2}€{"€":2}𐍈{"𐍈":2}""", "aa αα €€ 𐍈𐍈 " + # Invalid unicode sequences should be handled as plain strings. + # Invalid examples taken from: https://stackoverflow.com/a/3886015/1804173 + let invalidUtf8 = [ + "\xc3\x28", "\xa0\xa1", + "\xe2\x28\xa1", "\xe2\x82\x28", + "\xf0\x28\x8c\xbc", "\xf0\x90\x28\xbc", "\xf0\x28\x8c\x28" + ] + for s in invalidUtf8: + check &"{s:>5}", repeat(" ", 5-s.len) & s + + if pattern.kind notin {nnkStrLit..nnkTripleStrLit}: + error "& only works with string literals", pattern + let f = pattern.strVal + var i = 0 + let res = genSym(nskVar, "fmtRes") + result = newNimNode(nnkStmtListExpr, lineInfoFrom=pattern) + result.add newVarStmt(res, newCall(bindSym"newStringOfCap", newLit(f.len + count(f, '{')*10))) + var strlit = "" + while i < f.len: + if f[i] == '{': + inc i + if f[i] == '{': + inc i + strlit.add '{' + else: + if strlit.len > 0: + result.add newCall(bindSym"add", res, newLit(strlit)) + strlit = "" + + var subexpr = "" + while i < f.len and f[i] != '}' and f[i] != ':': + subexpr.add f[i] + inc i + let x = parseExpr(subexpr) + + if f[i] == ':': + inc i + var options = "" + while i < f.len and f[i] != '}': + options.add f[i] + inc i + result.add getAst(callFormatOption(res, x, newLit(options))) + else: + result.add getAst(callFormat(res, x)) + if f[i] == '}': + inc i + else: + doAssert false, "invalid format string: missing '}'" + elif f[i] == '}': + if f[i+1] == '}': + strlit.add '}' + inc i, 2 + else: + doAssert false, "invalid format string: '}' instead of '}}'" + inc i + else: + strlit.add f[i] + inc i + if strlit.len > 0: + result.add newCall(bindSym"add", res, newLit(strlit)) + result.add res + when defined(debugFmtDsl): + echo repr result + +template fmt*(pattern: string): untyped = + ## An alias for ``&``. + ## **Examples:** + ## + ## .. code-block:: nim + ## import json + ## import strformat except `&` + ## + ## let example = "oh, look no conflicts anymore" + ## echo fmt"{example}" + bind `&` + &pattern + +proc mkDigit(v: int, typ: char): string {.inline.} = + assert(v < 26) + if v < 10: + result = $chr(ord('0') + v) + else: + result = $chr(ord(if typ == 'x': 'a' else: 'A') + v - 10) + +proc alignString*(s: string, minimumWidth: int; align = '\0'; fill = ' '): string = + ## Aligns ``s`` using ``fill`` char. + ## This is only of interest if you want to write a custom ``format`` proc that + ## should support the standard format specifiers. + if minimumWidth == 0: + result = s + else: + let sRuneLen = if s.validateUtf8 == -1: s.runeLen else: s.len + let toFill = minimumWidth - sRuneLen + if toFill <= 0: + result = s + elif align == '<' or align == '\0': + result = s & repeat(fill, toFill) + elif align == '^': + let half = toFill div 2 + result = repeat(fill, half) & s & repeat(fill, toFill - half) + else: + result = repeat(fill, toFill) & s + +type + StandardFormatSpecifier* = object ## Type that describes "standard format specifiers". + fill*, align*: char ## Desired fill and alignment. + sign*: char ## Desired sign. + alternateForm*: bool ## Whether to prefix binary, octal and hex numbers + ## with ``0b``, ``0o``, ``0x``. + padWithZero*: bool ## Whether to pad with zeros rather than spaces. + minimumWidth*, precision*: int ## Desired minium width and precision. + typ*: char ## Type like 'f', 'g' or 'd'. + endPosition*: int ## End position in the format specifier after + ## ``parseStandardFormatSpecifier`` returned. + +proc formatInt(n: SomeNumber; radix: int; spec: StandardFormatSpecifier): string = + ## Converts ``n`` to string. If ``n`` is `SomeReal`, it casts to `int64`. + ## Conversion is done using ``radix``. If result's length is lesser than + ## ``minimumWidth``, it aligns result to the right or left (depending on ``a``) + ## with ``fill`` char. + when n is SomeUnsignedInt: + var v = n.uint64 + let negative = false + else: + var v = n.int64 + let negative = v.int64 < 0 + if negative: + # FIXME: overflow error for low(int64) + v = v * -1 + + var xx = "" + if spec.alternateForm: + case spec.typ + of 'X': xx = "0x" + of 'x': xx = "0x" + of 'b': xx = "0b" + of 'o': xx = "0o" + else: discard + + if v == 0: + result = "0" + else: + result = "" + while v > type(v)(0): + let d = v mod type(v)(radix) + v = v div type(v)(radix) + result.add(mkDigit(d.int, spec.typ)) + for idx in 0..<(result.len div 2): + swap result[idx], result[result.len - idx - 1] + if spec.padWithZero: + let sign = negative or spec.sign != '-' + let toFill = spec.minimumWidth - result.len - xx.len - ord(sign) + if toFill > 0: + result = repeat('0', toFill) & result + + if negative: + result = "-" & xx & result + elif spec.sign != '-': + result = spec.sign & xx & result + else: + result = xx & result + + if spec.align == '<': + for i in result.len.. 0: + result = repeat(spec.fill, toFill) & result + +proc parseStandardFormatSpecifier*(s: string; start = 0; + ignoreUnknownSuffix = false): StandardFormatSpecifier = + ## An exported helper proc that parses the "standard format specifiers", + ## as specified by the grammar:: + ## + ## [[fill]align][sign][#][0][minimumwidth][.precision][type] + ## + ## This is only of interest if you want to write a custom ``format`` proc that + ## should support the standard format specifiers. If ``ignoreUnknownSuffix`` is true, + ## an unknown suffix after the ``type`` field is not an error. + const alignChars = {'<', '>', '^'} + result.fill = ' ' + result.align = '\0' + result.sign = '-' + var i = start + if i + 1 < s.len and s[i+1] in alignChars: + result.fill = s[i] + result.align = s[i+1] + inc i, 2 + elif i < s.len and s[i] in alignChars: + result.align = s[i] + inc i + + if i < s.len and s[i] in {'-', '+', ' '}: + result.sign = s[i] + inc i + + if i < s.len and s[i] == '#': + result.alternateForm = true + inc i + + if i+1 < s.len and s[i] == '0' and s[i+1] in {'0'..'9'}: + result.padWithZero = true + inc i + + let parsedLength = parseSaturatedNatural(s, result.minimumWidth, i) + inc i, parsedLength + if i < s.len and s[i] == '.': + inc i + let parsedLengthB = parseSaturatedNatural(s, result.precision, i) + inc i, parsedLengthB + else: + result.precision = -1 + + if i < s.len and s[i] in {'A'..'Z', 'a'..'z'}: + result.typ = s[i] + inc i + result.endPosition = i + if i != s.len and not ignoreUnknownSuffix: + raise newException(ValueError, + "invalid format string, cannot parse: " & s[i..^1]) + + +proc format*(value: SomeInteger; specifier: string; res: var string) = + ## Standard format implementation for ``SomeInteger``. It makes little + ## sense to call this directly, but it is required to exist + ## by the ``&`` macro. + let spec = parseStandardFormatSpecifier(specifier) + var radix = 10 + case spec.typ + of 'x', 'X': radix = 16 + of 'd', '\0': discard + of 'b': radix = 2 + of 'o': radix = 8 + else: + raise newException(ValueError, + "invalid type in format string for number, expected one " & + " of 'x', 'X', 'b', 'd', 'o' but got: " & spec.typ) + res.add formatInt(value, radix, spec) + +proc format*(value: SomeReal; specifier: string; res: var string) = + ## Standard format implementation for ``SomeReal``. It makes little + ## sense to call this directly, but it is required to exist + ## by the ``&`` macro. + let spec = parseStandardFormatSpecifier(specifier) + + var fmode = ffDefault + case spec.typ + of 'e', 'E': + fmode = ffScientific + of 'f', 'F': + fmode = ffDecimal + of 'g', 'G': + fmode = ffDefault + of '\0': discard + else: + raise newException(ValueError, + "invalid type in format string for number, expected one " & + " of 'e', 'E', 'f', 'F', 'g', 'G' but got: " & spec.typ) + + var f = formatBiggestFloat(value, fmode, spec.precision) + if value >= 0.0 and spec.sign != '-': + f = spec.sign & f + # the default for numbers is right-alignment: + let align = if spec.align == '\0': '>' else: spec.align + let result = alignString(f, spec.minimumWidth, + align, spec.fill) + if spec.typ in {'A'..'Z'}: + res.add toUpperAscii(result) + else: + res.add result + +proc format*(value: string; specifier: string; res: var string) = + ## Standard format implementation for ``string``. It makes little + ## sense to call this directly, but it is required to exist + ## by the ``&`` macro. + let spec = parseStandardFormatSpecifier(specifier) + case spec.typ + of 's', '\0': discard + else: + raise newException(ValueError, + "invalid type in format string for string, expected 's', but got " & + spec.typ) + res.add alignString(value, spec.minimumWidth, spec.align, spec.fill) + +when isMainModule: + import json + + doAssert fmt"{'a'} {'b'}" == "a b" diff --git a/lib/pure/strscans.nim b/lib/pure/strscans.nim index f33e7451fc..2bd87837ff 100644 --- a/lib/pure/strscans.nim +++ b/lib/pure/strscans.nim @@ -31,7 +31,10 @@ As can be seen from the examples, strings are matched verbatim except for substrings starting with ``$``. These constructions are available: ================= ======================================================== -``$i`` Matches an integer. This uses ``parseutils.parseInt``. +``$b`` Matches a binary integer. This uses ``parseutils.parseBin``. +``$o`` Matches an octal integer. This uses ``parseutils.parseOct``. +``$i`` Matches a decimal integer. This uses ``parseutils.parseInt``. +``$h`` Matches a hex integer. This uses ``parseutils.parseHex``. ``$f`` Matches a floating pointer number. Uses ``parseFloat``. ``$w`` Matches an ASCII identifier: ``[A-Z-a-z_][A-Za-z_0-9]*``. ``$s`` Skips optional whitespace. @@ -330,19 +333,37 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b conds.add resLen.notZero conds.add resLen of 'w': - if i < results.len or getType(results[i]).typeKind != ntyString: + if i < results.len and getType(results[i]).typeKind == ntyString: matchBind "parseIdent" else: error("no string var given for $w") inc i + of 'b': + if i < results.len and getType(results[i]).typeKind == ntyInt: + matchBind "parseBin" + else: + error("no int var given for $b") + inc i + of 'o': + if i < results.len and getType(results[i]).typeKind == ntyInt: + matchBind "parseOct" + else: + error("no int var given for $o") + inc i of 'i': - if i < results.len or getType(results[i]).typeKind != ntyInt: + if i < results.len and getType(results[i]).typeKind == ntyInt: matchBind "parseInt" else: - error("no int var given for $d") + error("no int var given for $i") + inc i + of 'h': + if i < results.len and getType(results[i]).typeKind == ntyInt: + matchBind "parseHex" + else: + error("no int var given for $h") inc i of 'f': - if i < results.len or getType(results[i]).typeKind != ntyFloat: + if i < results.len and getType(results[i]).typeKind == ntyFloat: matchBind "parseFloat" else: error("no float var given for $f") @@ -357,7 +378,7 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b else: error("invalid format string") of '*', '+': - if i < results.len or getType(results[i]).typeKind != ntyString: + if i < results.len and getType(results[i]).typeKind == ntyString: var min = ord(pattern[p] == '+') var q=p+1 var token = "" @@ -441,7 +462,7 @@ template success*(x: int): bool = x != 0 template nxt*(input: string; idx, step: int = 1) = inc(idx, step) macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool = - ## See top level documentation of his module of how ``scanp`` works. + ## ``scanp`` is currently undocumented. type StmtTriple = tuple[init, cond, action: NimNode] template interf(x): untyped = bindSym(x, brForceOpen) @@ -645,6 +666,14 @@ when isMainModule: doAssert intval == 89 doAssert floatVal == 33.25 + var binval: int + var octval: int + var hexval: int + doAssert scanf("0b0101 0o1234 0xabcd", "$b$s$o$s$h", binval, octval, hexval) + doAssert binval == 0b0101 + doAssert octval == 0o1234 + doAssert hexval == 0xabcd + let xx = scanf("$abc", "$$$i", intval) doAssert xx == false diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 62ceaa2e8b..dbb4db7811 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1761,29 +1761,15 @@ proc insertSep*(s: string, sep = '_', digits = 3): string {.noSideEffect, proc escape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, rtl, extern: "nsuEscape".} = - ## Escapes a string `s`. + ## Escapes a string `s`. See `system.addEscapedChar `_ + ## for the escaping scheme. ## - ## This does these operations (at the same time): - ## * replaces any ``\`` by ``\\`` - ## * replaces any ``'`` by ``\'`` - ## * replaces any ``"`` by ``\"`` - ## * replaces any other character in the set ``{'\0'..'\31', '\127'..'\255'}`` - ## by ``\xHH`` where ``HH`` is its hexadecimal value. - ## The procedure has been designed so that its output is usable for many - ## different common syntaxes. The resulting string is prefixed with - ## `prefix` and suffixed with `suffix`. Both may be empty strings. - ## **Note**: This is not correct for producing Ansi C code! + ## The resulting string is prefixed with `prefix` and suffixed with `suffix`. + ## Both may be empty strings. result = newStringOfCap(s.len + s.len shr 2) result.add(prefix) for c in items(s): - case c - of '\0'..'\31', '\127'..'\255': - add(result, "\\x") - add(result, toHex(ord(c), 2)) - of '\\': add(result, "\\\\") - of '\'': add(result, "\\'") - of '\"': add(result, "\\\"") - else: add(result, c) + result.addEscapedChar(c) add(result, suffix) proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index 871ac5d391..d9600a3a1d 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -391,12 +391,11 @@ proc eraseLine*(f: File) = origin.X = 0'i16 if setConsoleCursorPosition(h, origin) == 0: raiseOSError(osLastError()) - var ht = scrbuf.dwSize.Y - origin.Y - var wt = scrbuf.dwSize.X - origin.X - if fillConsoleOutputCharacter(h, ' ', ht*wt, + var wt: DWORD = scrbuf.dwSize.X - origin.X + if fillConsoleOutputCharacter(h, ' ', wt, origin, addr(numwrote)) == 0: raiseOSError(osLastError()) - if fillConsoleOutputAttribute(h, scrbuf.wAttributes, ht * wt, + if fillConsoleOutputAttribute(h, scrbuf.wAttributes, wt, scrbuf.dwCursorPosition, addr(numwrote)) == 0: raiseOSError(osLastError()) else: @@ -634,7 +633,10 @@ proc getch*(): char = doAssert(readConsoleInput(fd, addr(keyEvent), 1, addr(numRead)) != 0) if numRead == 0 or keyEvent.eventType != 1 or keyEvent.bKeyDown == 0: continue - return char(keyEvent.uChar) + if keyEvent.uChar == 0: + return char(keyEvent.wVirtualKeyCode) + else: + return char(keyEvent.uChar) else: let fd = getFileHandle(stdin) var oldMode: Termios @@ -650,10 +652,10 @@ template setCursorPos*(x, y: int) = setCursorPos(stdout, x, y) template setCursorXPos*(x: int) = setCursorXPos(stdout, x) when defined(windows): template setCursorYPos(x: int) = setCursorYPos(stdout, x) -template cursorUp*(count=1) = cursorUp(stdout, f) -template cursorDown*(count=1) = cursorDown(stdout, f) -template cursorForward*(count=1) = cursorForward(stdout, f) -template cursorBackward*(count=1) = cursorBackward(stdout, f) +template cursorUp*(count=1) = cursorUp(stdout, count) +template cursorDown*(count=1) = cursorDown(stdout, count) +template cursorForward*(count=1) = cursorForward(stdout, count) +template cursorBackward*(count=1) = cursorBackward(stdout, count) template eraseLine*() = eraseLine(stdout) template eraseScreen*() = eraseScreen(stdout) template setStyle*(style: set[Style]) = diff --git a/lib/pure/times.nim b/lib/pure/times.nim index c1d6c3e530..50bf8f7f2f 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -10,27 +10,25 @@ ## This module contains routines and types for dealing with time. ## This module is available for the `JavaScript target -## `_. +## `_. The proleptic Gregorian calendar is the only calendar supported. ## ## Examples: ## ## .. code-block:: nim ## ## import times, os -## var -## t = cpuTime() +## let time = cpuTime() ## ## sleep(100) # replace this with something to be timed -## echo "Time taken: ",cpuTime() - t +## echo "Time taken: ",cpuTime() - time ## -## echo "My formatted time: ", format(getLocalTime(getTime()), "d MMMM yyyy HH:mm") +## echo "My formatted time: ", format(now(), "d MMMM yyyy HH:mm") ## echo "Using predefined formats: ", getClockStr(), " ", getDateStr() ## ## echo "epochTime() float value: ", epochTime() -## echo "getTime() float value: ", toSeconds(getTime()) ## echo "cpuTime() float value: ", cpuTime() -## echo "An hour from now : ", getLocalTime(getTime()) + 1.hours -## echo "An hour from (UTC) now: ", getGmTime(getTime()) + initInterval(0,0,0,1) +## echo "An hour from now : ", now() + 1.hours +## echo "An hour from (UTC) now: ", getTime().utc + initInterval(0,0,0,1) {.push debugger:off.} # the user does not want to trace a part # of the standard library! @@ -40,132 +38,85 @@ import include "system/inclrtl" -type - Month* = enum ## represents a month - mJan, mFeb, mMar, mApr, mMay, mJun, mJul, mAug, mSep, mOct, mNov, mDec - WeekDay* = enum ## represents a weekday - dMon, dTue, dWed, dThu, dFri, dSat, dSun +when defined(posix): + import posix -when defined(posix) and not defined(JS): - when defined(linux) and defined(amd64): - type - TimeImpl {.importc: "time_t", header: "".} = clong - Time* = distinct TimeImpl ## distinct type that represents a time - ## measured as number of seconds since the epoch - - Timeval {.importc: "struct timeval", - header: "".} = object ## struct timeval - tv_sec: clong ## Seconds. - tv_usec: clong ## Microseconds. - else: - type - TimeImpl {.importc: "time_t", header: "".} = int - Time* = distinct TimeImpl ## distinct type that represents a time - ## measured as number of seconds since the epoch - - Timeval {.importc: "struct timeval", - header: "".} = object ## struct timeval - tv_sec: int ## Seconds. - tv_usec: int ## Microseconds. - - # we cannot import posix.nim here, because posix.nim depends on times.nim. - # Ok, we could, but I don't want circular dependencies. - # And gettimeofday() is not defined in the posix module anyway. Sigh. + type CTime = posix.Time proc posix_gettimeofday(tp: var Timeval, unused: pointer = nil) {. importc: "gettimeofday", header: "".} when not defined(freebsd) and not defined(netbsd) and not defined(openbsd): var timezone {.importc, header: "".}: int - proc tzset(): void {.importc, header: "".} tzset() elif defined(windows): import winlean # newest version of Visual C++ defines time_t to be of 64 bits - type TimeImpl {.importc: "time_t", header: "".} = int64 + type CTime {.importc: "time_t", header: "".} = distinct int64 # visual c's c runtime exposes these under a different name - var - timezone {.importc: "_timezone", header: "".}: int - - type - Time* = distinct TimeImpl - - -elif defined(JS): - type - TimeBase = float - Time* = distinct TimeBase - - proc getDay(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getFullYear(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getHours(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getMilliseconds(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getMinutes(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getMonth(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getSeconds(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getTime(t: Time): int {.tags: [], raises: [], noSideEffect, benign, importcpp.} - proc getTimezoneOffset(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getDate(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCDate(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCFullYear(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCHours(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCMilliseconds(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCMinutes(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCMonth(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCSeconds(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCDay(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getYear(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc parse(t: Time; s: cstring): Time {.tags: [], raises: [], benign, importcpp.} - proc setDate(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setFullYear(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setHours(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setMilliseconds(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setMinutes(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setMonth(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setSeconds(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setTime(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setUTCDate(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setUTCFullYear(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setUTCHours(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setUTCMilliseconds(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setUTCMinutes(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setUTCMonth(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setUTCSeconds(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setYear(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc toGMTString(t: Time): cstring {.tags: [], raises: [], benign, importcpp.} - proc toLocaleString(t: Time): cstring {.tags: [], raises: [], benign, importcpp.} + var timezone {.importc: "_timezone", header: "".}: int type - TimeInfo* = object of RootObj ## represents a time in different parts - second*: range[0..61] ## The number of seconds after the minute, - ## normally in the range 0 to 59, but can - ## be up to 61 to allow for leap seconds. - minute*: range[0..59] ## The number of minutes after the hour, - ## in the range 0 to 59. - hour*: range[0..23] ## The number of hours past midnight, - ## in the range 0 to 23. - monthday*: range[1..31] ## The day of the month, in the range 1 to 31. - month*: Month ## The current month. - year*: int ## The current year. - weekday*: WeekDay ## The current day of the week. - yearday*: range[0..365] ## The number of days since January 1, - ## in the range 0 to 365. - ## Always 0 if the target is JS. - isDST*: bool ## Determines whether DST is in effect. - ## Semantically, this adds another negative hour - ## offset to the time in addition to the timezone. - timezone*: int ## The offset of the (non-DST) timezone in seconds - ## west of UTC. Note that the sign of this number - ## is the opposite of the one in a formatted - ## timezone string like ``+01:00`` (which would be - ## parsed into the timezone ``-3600``). + Month* = enum ## Represents a month. Note that the enum starts at ``1``, so ``ord(month)`` will give + ## the month number in the range ``[1..12]``. + mJan = 1, mFeb, mMar, mApr, mMay, mJun, mJul, mAug, mSep, mOct, mNov, mDec - ## I make some assumptions about the data in here. Either - ## everything should be positive or everything negative. Zero is - ## fine too. Mixed signs will lead to unexpected results. - TimeInterval* = object ## a time interval + WeekDay* = enum ## Represents a weekday. + dMon, dTue, dWed, dThu, dFri, dSat, dSun + + MonthdayRange* = range[1..31] + HourRange* = range[0..23] + MinuteRange* = range[0..59] + SecondRange* = range[0..60] + YeardayRange* = range[0..365] + + TimeImpl = int64 + + Time* = distinct TimeImpl ## Represents a point in time. + ## This is currently implemented as a ``int64`` representing + ## seconds since ``1970-01-01T00:00:00Z``, but don't + ## rely on this knowledge because it might change + ## in the future to allow for higher precision. + ## Use the procs ``toUnix`` and ``fromUnix`` to + ## work with unix timestamps instead. + + DateTime* = object of RootObj ## Represents a time in different parts. + ## Although this type can represent leap + ## seconds, they are generally not supported + ## in this module. They are not ignored, + ## but the ``DateTime``'s returned by + ## procedures in this module will never have + ## a leap second. + second*: SecondRange ## The number of seconds after the minute, + ## normally in the range 0 to 59, but can + ## be up to 60 to allow for a leap second. + minute*: MinuteRange ## The number of minutes after the hour, + ## in the range 0 to 59. + hour*: HourRange ## The number of hours past midnight, + ## in the range 0 to 23. + monthday*: MonthdayRange ## The day of the month, in the range 1 to 31. + month*: Month ## The current month. + year*: int ## The current year, using astronomical year numbering + ## (meaning that before year 1 is year 0, then year -1 and so on). + weekday*: WeekDay ## The current day of the week. + yearday*: YeardayRange ## The number of days since January 1, + ## in the range 0 to 365. + isDst*: bool ## Determines whether DST is in effect. + ## Always false for the JavaScript backend. + timezone*: Timezone ## The timezone represented as an implementation of ``Timezone``. + utcOffset*: int ## The offset in seconds west of UTC, including any offset due to DST. + ## Note that the sign of this number is the opposite + ## of the one in a formatted offset string like ``+01:00`` + ## (which would be parsed into the UTC offset ``-3600``). + + TimeInterval* = object ## Represents a duration of time. Can be used to add and subtract + ## from a ``DateTime`` or ``Time``. + ## Note that a ``TimeInterval`` doesn't represent a fixed duration of time, + ## since the duration of some units depend on the context (e.g a year + ## can be either 365 or 366 days long). The non-fixed time units are years, + ## months and days. milliseconds*: int ## The number of milliseconds seconds*: int ## The number of seconds minutes*: int ## The number of minutes @@ -174,92 +125,394 @@ type months*: int ## The number of months years*: int ## The number of years + Timezone* = object ## Timezone interface for supporting ``DateTime``'s of arbritary timezones. + ## The ``times`` module only supplies implementations for the systems local time and UTC. + ## The members ``zoneInfoFromUtc`` and ``zoneInfoFromTz`` should not be accessed directly + ## and are only exported so that ``Timezone`` can be implemented by other modules. + zoneInfoFromUtc*: proc (time: Time): ZonedTime {.tags: [], raises: [], benign.} + zoneInfoFromTz*: proc (adjTime: Time): ZonedTime {.tags: [], raises: [], benign.} + name*: string ## The name of the timezone, f.ex 'Europe/Stockholm' or 'Etc/UTC'. Used for checking equality. + ## Se also: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + ZonedTime* = object ## Represents a zooned instant in time that is not associated with any calendar. + ## This type is only used for implementing timezones. + adjTime*: Time ## Time adjusted to a timezone. + utcOffset*: int + isDst*: bool + {.deprecated: [TMonth: Month, TWeekDay: WeekDay, TTime: Time, - TTimeInterval: TimeInterval, TTimeInfo: TimeInfo].} + TTimeInterval: TimeInterval, TTimeInfo: DateTime, TimeInfo: DateTime].} -proc getTime*(): Time {.tags: [TimeEffect], benign.} - ## gets the current calendar time as a UNIX epoch value (number of seconds - ## elapsed since 1970) with integer precission. Use epochTime for higher - ## resolution. -proc getLocalTime*(t: Time): TimeInfo {.tags: [TimeEffect], raises: [], benign.} - ## converts the calendar time `t` to broken-time representation, - ## expressed relative to the user's specified time zone. -proc getGMTime*(t: Time): TimeInfo {.tags: [TimeEffect], raises: [], benign.} - ## converts the calendar time `t` to broken-down time representation, - ## expressed in Coordinated Universal Time (UTC). +const + secondsInMin = 60 + secondsInHour = 60*60 + secondsInDay = 60*60*24 + minutesInHour = 60 -proc timeInfoToTime*(timeInfo: TimeInfo): Time - {.tags: [TimeEffect], benign, deprecated.} - ## converts a broken-down time structure to - ## calendar time representation. The function ignores the specified - ## contents of the structure members `weekday` and `yearday` and recomputes - ## them from the other information in the broken-down time structure. - ## - ## **Warning:** This procedure is deprecated since version 0.14.0. - ## Use ``toTime`` instead. +proc fromUnix*(unix: int64): Time {.benign, tags: [], raises: [], noSideEffect.} = + ## Convert a unix timestamp (seconds since ``1970-01-01T00:00:00Z``) to a ``Time``. + Time(unix) -proc toTime*(timeInfo: TimeInfo): Time {.tags: [TimeEffect], benign.} - ## converts a broken-down time structure to - ## calendar time representation. The function ignores the specified - ## contents of the structure members `weekday` and `yearday` and recomputes - ## them from the other information in the broken-down time structure. +proc toUnix*(t: Time): int64 {.benign, tags: [], raises: [], noSideEffect.} = + ## Convert ``t`` to a unix timestamp (seconds since ``1970-01-01T00:00:00Z``). + t.int64 -proc fromSeconds*(since1970: float): Time {.tags: [], raises: [], benign.} - ## Takes a float which contains the number of seconds since the unix epoch and - ## returns a time object. +proc isLeapYear*(year: int): bool = + ## Returns true if ``year`` is a leap year. + year mod 4 == 0 and (year mod 100 != 0 or year mod 400 == 0) -proc fromSeconds*(since1970: int64): Time {.tags: [], raises: [], benign.} = - ## Takes an int which contains the number of seconds since the unix epoch and - ## returns a time object. - fromSeconds(float(since1970)) +proc getDaysInMonth*(month: Month, year: int): int = + ## Get the number of days in a ``month`` of a ``year``. + # http://www.dispersiondesign.com/articles/time/number_of_days_in_a_month + case month + of mFeb: result = if isLeapYear(year): 29 else: 28 + of mApr, mJun, mSep, mNov: result = 30 + else: result = 31 -proc toSeconds*(time: Time): float {.tags: [], raises: [], benign.} - ## Returns the time in seconds since the unix epoch. +proc getDaysInYear*(year: int): int = + ## Get the number of days in a ``year`` + result = 365 + (if isLeapYear(year): 1 else: 0) + +proc assertValidDate(monthday: MonthdayRange, month: Month, year: int) {.inline.} = + assert monthday <= getDaysInMonth(month, year), + $year & "-" & $ord(month) & "-" & $monthday & " is not a valid date" + +proc toEpochDay(monthday: MonthdayRange, month: Month, year: int): int64 = + ## Get the epoch day from a year/month/day date. + ## The epoch day is the number of days since 1970/01/01 (it might be negative). + assertValidDate monthday, month, year + # Based on http://howardhinnant.github.io/date_algorithms.html + var (y, m, d) = (year, ord(month), monthday.int) + if m <= 2: + y.dec + + let era = (if y >= 0: y else: y-399) div 400 + let yoe = y - era * 400 + let doy = (153 * (m + (if m > 2: -3 else: 9)) + 2) div 5 + d-1 + let doe = yoe * 365 + yoe div 4 - yoe div 100 + doy + return era * 146097 + doe - 719468 + +proc fromEpochDay(epochday: int64): tuple[monthday: MonthdayRange, month: Month, year: int] = + ## Get the year/month/day date from a epoch day. + ## The epoch day is the number of days since 1970/01/01 (it might be negative). + # Based on http://howardhinnant.github.io/date_algorithms.html + var z = epochday + z.inc 719468 + let era = (if z >= 0: z else: z - 146096) div 146097 + let doe = z - era * 146097 + let yoe = (doe - doe div 1460 + doe div 36524 - doe div 146096) div 365 + let y = yoe + era * 400; + let doy = doe - (365 * yoe + yoe div 4 - yoe div 100) + let mp = (5 * doy + 2) div 153 + let d = doy - (153 * mp + 2) div 5 + 1 + let m = mp + (if mp < 10: 3 else: -9) + return (d.MonthdayRange, m.Month, (y + ord(m <= 2)).int) + +proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int): YeardayRange {.tags: [], raises: [], benign .} = + ## Returns the day of the year. + ## Equivalent with ``initDateTime(day, month, year).yearday``. + assertValidDate monthday, month, year + const daysUntilMonth: array[Month, int] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] + const daysUntilMonthLeap: array[Month, int] = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335] + + if isLeapYear(year): + result = daysUntilMonthLeap[month] + monthday - 1 + else: + result = daysUntilMonth[month] + monthday - 1 + +proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay {.tags: [], raises: [], benign .} = + ## Returns the day of the week enum from day, month and year. + ## Equivalent with ``initDateTime(day, month, year).weekday``. + assertValidDate monthday, month, year + # 1970-01-01 is a Thursday, we adjust to the previous Monday + let days = toEpochday(monthday, month, year) - 3 + let weeks = (if days >= 0: days else: days - 6) div 7 + let wd = days - weeks * 7 + # The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc. + # so we must correct for the WeekDay type. + result = if wd == 0: dSun else: WeekDay(wd - 1) + +# Forward declarations +proc utcZoneInfoFromUtc(time: Time): ZonedTime {.tags: [], raises: [], benign .} +proc utcZoneInfoFromTz(adjTime: Time): ZonedTime {.tags: [], raises: [], benign .} +proc localZoneInfoFromUtc(time: Time): ZonedTime {.tags: [], raises: [], benign .} +proc localZoneInfoFromTz(adjTime: Time): ZonedTime {.tags: [], raises: [], benign .} proc `-`*(a, b: Time): int64 {. - rtl, extern: "ntDiffTime", tags: [], raises: [], noSideEffect, benign.} - ## computes the difference of two calendar times. Result is in seconds. + rtl, extern: "ntDiffTime", tags: [], raises: [], noSideEffect, benign, deprecated.} = + ## Computes the difference of two calendar times. Result is in seconds. + ## This is deprecated because it will need to change when sub second time resolution is implemented. + ## Use ``a.toUnix - b.toUnix`` instead. ## ## .. code-block:: nim ## let a = fromSeconds(1_000_000_000) ## let b = fromSeconds(1_500_000_000) ## echo initInterval(seconds=int(b - a)) ## # (milliseconds: 0, seconds: 20, minutes: 53, hours: 0, days: 5787, months: 0, years: 0) + a.toUnix - b.toUnix proc `<`*(a, b: Time): bool {. - rtl, extern: "ntLtTime", tags: [], raises: [], noSideEffect.} = - ## returns true iff ``a < b``, that is iff a happened before b. - when defined(js): - result = TimeBase(a) < TimeBase(b) - else: - result = a - b < 0 + rtl, extern: "ntLtTime", tags: [], raises: [], noSideEffect, borrow.} + ## Returns true iff ``a < b``, that is iff a happened before b. proc `<=` * (a, b: Time): bool {. - rtl, extern: "ntLeTime", tags: [], raises: [], noSideEffect.}= - ## returns true iff ``a <= b``. - when defined(js): - result = TimeBase(a) <= TimeBase(b) - else: - result = a - b <= 0 + rtl, extern: "ntLeTime", tags: [], raises: [], noSideEffect, borrow.} + ## Returns true iff ``a <= b``. proc `==`*(a, b: Time): bool {. - rtl, extern: "ntEqTime", tags: [], raises: [], noSideEffect.} = - ## returns true if ``a == b``, that is if both times represent the same value - when defined(js): - result = TimeBase(a) == TimeBase(b) + rtl, extern: "ntEqTime", tags: [], raises: [], noSideEffect, borrow.} + ## Returns true if ``a == b``, that is if both times represent the same point in time. + +proc toTime*(dt: DateTime): Time {.tags: [], raises: [], benign.} = + ## Converts a broken-down time structure to + ## calendar time representation. + let epochDay = toEpochday(dt.monthday, dt.month, dt.year) + result = Time(epochDay * secondsInDay) + result.inc dt.hour * secondsInHour + result.inc dt.minute * 60 + result.inc dt.second + # The code above ignores the UTC offset of `timeInfo`, + # so we need to compensate for that here. + result.inc dt.utcOffset + +proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime = + let adjTime = zt.adjTime.int64 + let epochday = (if adjTime >= 0: adjTime else: adjTime - (secondsInDay - 1)) div secondsInDay + var rem = zt.adjTime.int64 - epochday * secondsInDay + let hour = rem div secondsInHour + rem = rem - hour * secondsInHour + let minute = rem div secondsInMin + rem = rem - minute * secondsInMin + let second = rem + + let (d, m, y) = fromEpochday(epochday) + + DateTime( + year: y, + month: m, + monthday: d, + hour: hour, + minute: minute, + second: second, + weekday: getDayOfWeek(d, m, y), + yearday: getDayOfYear(d, m, y), + isDst: zt.isDst, + timezone: zone, + utcOffset: zt.utcOffset + ) + +proc inZone*(time: Time, zone: Timezone): DateTime {.tags: [], raises: [], benign.} = + ## Break down ``time`` into a ``DateTime`` using ``zone`` as the timezone. + let zoneInfo = zone.zoneInfoFromUtc(time) + result = initDateTime(zoneInfo, zone) + +proc inZone*(dt: DateTime, zone: Timezone): DateTime {.tags: [], raises: [], benign.} = + ## Convert ``dt`` into a ``DateTime`` using ``zone`` as the timezone. + dt.toTime.inZone(zone) + +proc `$`*(zone: Timezone): string = + ## Returns the name of the timezone. + zone.name + +proc `==`*(zone1, zone2: Timezone): bool = + ## Two ``Timezone``'s are considered equal if their name is equal. + zone1.name == zone2.name + +proc toAdjTime(dt: DateTime): Time = + let epochDay = toEpochday(dt.monthday, dt.month, dt.year) + result = Time(epochDay * secondsInDay) + result.inc dt.hour * secondsInHour + result.inc dt.minute * secondsInMin + result.inc dt.second + +when defined(JS): + type JsDate = object + proc newDate(year, month, date, hours, minutes, seconds, milliseconds: int): JsDate {.tags: [], raises: [], importc: "new Date".} + proc newDate(): JsDate {.importc: "new Date".} + proc newDate(value: float): JsDate {.importc: "new Date".} + proc getTimezoneOffset(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getDay(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getFullYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getHours(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getMilliseconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getMinutes(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getMonth(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getSeconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getTime(js: JsDate): int {.tags: [], raises: [], noSideEffect, benign, importcpp.} + proc getDate(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCDate(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCFullYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCHours(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCMilliseconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCMinutes(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCMonth(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCSeconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCDay(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc setFullYear(js: JsDate, year: int): void {.tags: [], raises: [], benign, importcpp.} + + proc localZoneInfoFromUtc(time: Time): ZonedTime = + let jsDate = newDate(time.float * 1000) + let offset = jsDate.getTimezoneOffset() * secondsInMin + result.adjTime = Time(time.int64 - offset) + result.utcOffset = offset + result.isDst = false + + proc localZoneInfoFromTz(adjTime: Time): ZonedTime = + let utcDate = newDate(adjTime.float * 1000) + let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), utcDate.getUTCDate(), + utcDate.getUTCHours(), utcDate.getUTCMinutes(), utcDate.getUTCSeconds(), 0) + + # This is as dumb as it looks - JS doesn't support years in the range 0-99 in the constructor + # because they are assumed to be 19xx... + # Because JS doesn't support timezone history, it doesn't really matter in practice. + if utcDate.getUTCFullYear() in 0 .. 99: + localDate.setFullYear(utcDate.getUTCFullYear()) + + result.adjTime = adjTime + result.utcOffset = localDate.getTimezoneOffset() * secondsInMin + result.isDst = false + +else: + when defined(freebsd) or defined(netbsd) or defined(openbsd) or + defined(macosx): + type + StructTm {.importc: "struct tm".} = object + second {.importc: "tm_sec".}, + minute {.importc: "tm_min".}, + hour {.importc: "tm_hour".}, + monthday {.importc: "tm_mday".}, + month {.importc: "tm_mon".}, + year {.importc: "tm_year".}, + weekday {.importc: "tm_wday".}, + yearday {.importc: "tm_yday".}, + isdst {.importc: "tm_isdst".}: cint + gmtoff {.importc: "tm_gmtoff".}: clong else: - result = a - b == 0 + type + StructTm {.importc: "struct tm".} = object + second {.importc: "tm_sec".}, + minute {.importc: "tm_min".}, + hour {.importc: "tm_hour".}, + monthday {.importc: "tm_mday".}, + month {.importc: "tm_mon".}, + year {.importc: "tm_year".}, + weekday {.importc: "tm_wday".}, + yearday {.importc: "tm_yday".}, + isdst {.importc: "tm_isdst".}: cint + when defined(linux) and defined(amd64): + gmtoff {.importc: "tm_gmtoff".}: clong + zone {.importc: "tm_zone".}: cstring + type + StructTmPtr = ptr StructTm -proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign.} - ## returns the offset of the local (non-DST) timezone in seconds west of UTC. + proc localtime(timer: ptr CTime): StructTmPtr {. importc: "localtime", header: "", tags: [].} -proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} - ## get the milliseconds from the start of the program. **Deprecated since - ## version 0.8.10.** Use ``epochTime`` or ``cpuTime`` instead. + proc toAdjTime(tm: StructTm): Time = + let epochDay = toEpochday(tm.monthday, (tm.month + 1).Month, tm.year.int + 1900) + result = Time(epochDay * secondsInDay) + result.inc tm.hour * secondsInHour + result.inc tm.minute * 60 + result.inc tm.second + + proc getStructTm(time: Time | int64): StructTm = + let timei64 = time.int64 + var a = + if timei64 < low(CTime): + CTime(low(CTime)) + elif timei64 > high(CTime): + CTime(high(CTime)) + else: + CTime(timei64) + result = localtime(addr(a))[] + + proc localZoneInfoFromUtc(time: Time): ZonedTime = + let tm = getStructTm(time) + let adjTime = tm.toAdjTime + result.adjTime = adjTime + result.utcOffset = (time.toUnix - adjTime.toUnix).int + result.isDst = tm.isdst > 0 + + proc localZoneInfoFromTz(adjTime: Time): ZonedTime = + var adjTimei64 = adjTime.int64 + let past = adjTimei64 - secondsInDay + var tm = getStructTm(past) + let pastOffset = past - tm.toAdjTime.int64 + + let future = adjTimei64 + secondsInDay + tm = getStructTm(future) + let futureOffset = future - tm.toAdjTime.int64 + + var utcOffset: int + if pastOffset == futureOffset: + utcOffset = pastOffset.int + else: + if pastOffset > futureOffset: + adjTimei64 -= secondsInHour + + adjTimei64 += pastOffset + utcOffset = (adjTimei64 - getStructTm(adjTimei64).toAdjTime.int64).int + + # This extra roundtrip is needed to normalize any impossible datetimes + # as a result of offset changes (normally due to dst) + let utcTime = adjTime.int64 + utcOffset + tm = getStructTm(utcTime) + result.adjTime = tm.toAdjTime + result.utcOffset = (utcTime - result.adjTime.int64).int + result.isDst = tm.isdst > 0 + +proc utcZoneInfoFromUtc(time: Time): ZonedTime = + result.adjTime = time + result.utcOffset = 0 + result.isDst = false + +proc utcZoneInfoFromTz(adjTime: Time): ZonedTime = + utcZoneInfoFromUtc(adjTime) # adjTime == time since we are in UTC + +proc utc*(): TimeZone = + ## Get the ``Timezone`` implementation for the UTC timezone. + ## + ## .. code-block:: nim + ## doAssert now().utc.timezone == utc() + ## doAssert utc().name == "Etc/UTC" + Timezone(zoneInfoFromUtc: utcZoneInfoFromUtc, zoneInfoFromTz: utcZoneInfoFromTz, name: "Etc/UTC") + +proc local*(): TimeZone = + ## Get the ``Timezone`` implementation for the local timezone. + ## + ## .. code-block:: nim + ## doAssert now().timezone == local() + ## doAssert local().name == "LOCAL" + Timezone(zoneInfoFromUtc: localZoneInfoFromUtc, zoneInfoFromTz: localZoneInfoFromTz, name: "LOCAL") + +proc utc*(dt: DateTime): DateTime = + ## Shorthand for ``dt.inZone(utc())``. + dt.inZone(utc()) + +proc local*(dt: DateTime): DateTime = + ## Shorthand for ``dt.inZone(local())``. + dt.inZone(local()) + +proc utc*(t: Time): DateTime = + ## Shorthand for ``t.inZone(utc())``. + t.inZone(utc()) + +proc local*(t: Time): DateTime = + ## Shorthand for ``t.inZone(local())``. + t.inZone(local()) + +proc getTime*(): Time {.tags: [TimeEffect], benign.} + ## Gets the current time as a ``Time`` with second resolution. Use epochTime for higher + ## resolution. + +proc now*(): DateTime {.tags: [TimeEffect], benign.} = + ## Get the current time as a ``DateTime`` in the local timezone. + ## + ## Shorthand for ``getTime().local``. + getTime().local proc initInterval*(milliseconds, seconds, minutes, hours, days, months, years: int = 0): TimeInterval = - ## creates a new ``TimeInterval``. + ## Creates a new ``TimeInterval``. ## ## You can also use the convenience procedures called ``milliseconds``, ## ``seconds``, ``minutes``, ``hours``, ``days``, ``months``, and ``years``. @@ -269,46 +522,33 @@ proc initInterval*(milliseconds, seconds, minutes, hours, days, months, ## .. code-block:: nim ## ## let day = initInterval(hours=24) - ## let tomorrow = getTime() + day - ## echo(tomorrow) - var carryO = 0 - result.milliseconds = `mod`(milliseconds, 1000) - carryO = `div`(milliseconds, 1000) - result.seconds = `mod`(carryO + seconds, 60) - carryO = `div`(carryO + seconds, 60) - result.minutes = `mod`(carryO + minutes, 60) - carryO = `div`(carryO + minutes, 60) - result.hours = `mod`(carryO + hours, 24) - carryO = `div`(carryO + hours, 24) - result.days = carryO + days - - result.months = `mod`(months, 12) - carryO = `div`(months, 12) - result.years = carryO + years + ## let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc()) + ## doAssert $(dt + day) == "2000-01-02T12-00-00+00:00" + result.milliseconds = milliseconds + result.seconds = seconds + result.minutes = minutes + result.hours = hours + result.days = days + result.months = months + result.years = years proc `+`*(ti1, ti2: TimeInterval): TimeInterval = ## Adds two ``TimeInterval`` objects together. - var carryO = 0 - result.milliseconds = `mod`(ti1.milliseconds + ti2.milliseconds, 1000) - carryO = `div`(ti1.milliseconds + ti2.milliseconds, 1000) - result.seconds = `mod`(carryO + ti1.seconds + ti2.seconds, 60) - carryO = `div`(carryO + ti1.seconds + ti2.seconds, 60) - result.minutes = `mod`(carryO + ti1.minutes + ti2.minutes, 60) - carryO = `div`(carryO + ti1.minutes + ti2.minutes, 60) - result.hours = `mod`(carryO + ti1.hours + ti2.hours, 24) - carryO = `div`(carryO + ti1.hours + ti2.hours, 24) - result.days = carryO + ti1.days + ti2.days - - result.months = `mod`(ti1.months + ti2.months, 12) - carryO = `div`(ti1.months + ti2.months, 12) - result.years = carryO + ti1.years + ti2.years + result.milliseconds = ti1.milliseconds + ti2.milliseconds + result.seconds = ti1.seconds + ti2.seconds + result.minutes = ti1.minutes + ti2.minutes + result.hours = ti1.hours + ti2.hours + result.days = ti1.days + ti2.days + result.months = ti1.months + ti2.months + result.years = ti1.years + ti2.years proc `-`*(ti: TimeInterval): TimeInterval = ## Reverses a time interval + ## ## .. code-block:: nim ## ## let day = -initInterval(hours=24) - ## echo day # -> (milliseconds: 0, seconds: 0, minutes: 0, hours: 0, days: -1, months: 0, years: 0) + ## echo day # -> (milliseconds: 0, seconds: 0, minutes: 0, hours: -24, days: 0, months: 0, years: 0) result = TimeInterval( milliseconds: -ti.milliseconds, seconds: -ti.seconds, @@ -325,123 +565,100 @@ proc `-`*(ti1, ti2: TimeInterval): TimeInterval = ## Time components are compared one-by-one, see output: ## ## .. code-block:: nim - ## let a = fromSeconds(1_000_000_000) - ## let b = fromSeconds(1_500_000_000) + ## let a = fromUnix(1_000_000_000) + ## let b = fromUnix(1_500_000_000) ## echo b.toTimeInterval - a.toTimeInterval - ## # (milliseconds: 0, seconds: -40, minutes: -6, hours: 1, days: -2, months: -2, years: 16) + ## # (milliseconds: 0, seconds: -40, minutes: -6, hours: 1, days: 5, months: -2, years: 16) result = ti1 + (-ti2) -proc isLeapYear*(year: int): bool = - ## returns true if ``year`` is a leap year +proc evaluateInterval(dt: DateTime, interval: TimeInterval): tuple[adjDiff, absDiff: int64] = + ## Evaluates how many seconds the interval is worth + ## in the context of ``dt``. + ## The result in split into an adjusted diff and an absolute diff. - if year mod 400 == 0: - return true - elif year mod 100 == 0: - return false - elif year mod 4 == 0: - return true - else: - return false - -proc getDaysInMonth*(month: Month, year: int): int = - ## Get the number of days in a ``month`` of a ``year`` - - # http://www.dispersiondesign.com/articles/time/number_of_days_in_a_month - case month - of mFeb: result = if isLeapYear(year): 29 else: 28 - of mApr, mJun, mSep, mNov: result = 30 - else: result = 31 - -proc getDaysInYear*(year: int): int = - ## Get the number of days in a ``year`` - result = 365 + (if isLeapYear(year): 1 else: 0) - -proc toSeconds(a: TimeInfo, interval: TimeInterval): float = - ## Calculates how many seconds the interval is worth by adding up - ## all the fields - - var anew = a + var anew = dt var newinterv = interval - result = 0 newinterv.months += interval.years * 12 var curMonth = anew.month - if newinterv.months < 0: # subtracting + # Subtracting + if newinterv.months < 0: for mth in countDown(-1 * newinterv.months, 1): - result -= float(getDaysInMonth(curMonth, anew.year) * 24 * 60 * 60) if curMonth == mJan: curMonth = mDec anew.year.dec() else: curMonth.dec() - else: # adding + result.adjDiff -= getDaysInMonth(curMonth, anew.year) * secondsInDay + # Adding + else: for mth in 1 .. newinterv.months: - result += float(getDaysInMonth(curMonth, anew.year) * 24 * 60 * 60) + result.adjDiff += getDaysInMonth(curMonth, anew.year) * secondsInDay if curMonth == mDec: curMonth = mJan anew.year.inc() else: curMonth.inc() - result += float(newinterv.days * 24 * 60 * 60) - result += float(newinterv.hours * 60 * 60) - result += float(newinterv.minutes * 60) - result += float(newinterv.seconds) - result += newinterv.milliseconds / 1000 + result.adjDiff += newinterv.days * secondsInDay + result.absDiff += newinterv.hours * secondsInHour + result.absDiff += newinterv.minutes * secondsInMin + result.absDiff += newinterv.seconds + result.absDiff += newinterv.milliseconds div 1000 -proc `+`*(a: TimeInfo, interval: TimeInterval): TimeInfo = - ## adds ``interval`` time from TimeInfo ``a``. +proc `+`*(dt: DateTime, interval: TimeInterval): DateTime = + ## Adds ``interval`` to ``dt``. Components from ``interval`` are added + ## in the order of their size, i.e first the ``years`` component, then the ``months`` + ## component and so on. The returned ``DateTime`` will have the same timezone as the input. ## - ## **Note:** This has been only briefly tested and it may not be - ## very accurate. - let t = toSeconds(toTime(a)) - let secs = toSeconds(a, interval) - if a.timezone == 0: - result = getGMTime(fromSeconds(t + secs)) + ## Note that when adding months, monthday overflow is allowed. This means that if the resulting + ## month doesn't have enough days it, the month will be incremented and the monthday will be + ## set to the number of days overflowed. So adding one month to `31 October` will result in `31 November`, + ## which will overflow and result in `1 December`. + ## + ## .. code-block:: nim + ## let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) + ## doAssert $(dt + 1.months) == "2017-04-30T00:00:00+00:00" + ## # This is correct and happens due to monthday overflow. + ## doAssert $(dt - 1.months) == "2017-03-02T00:00:00+00:00" + let (adjDiff, absDiff) = evaluateInterval(dt, interval) + + if adjDiff.int64 != 0: + let zInfo = dt.timezone.zoneInfoFromTz(Time(dt.toAdjTime.int64 + adjDiff)) + + if absDiff != 0: + let time = Time(zInfo.adjTime.int64 + zInfo.utcOffset + absDiff) + result = initDateTime(dt.timezone.zoneInfoFromUtc(time), dt.timezone) + else: + result = initDateTime(zInfo, dt.timezone) else: - result = getLocalTime(fromSeconds(t + secs)) + result = initDateTime(dt.timezone.zoneInfoFromUtc(Time(dt.toTime.int64 + absDiff)), dt.timezone) -proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo = - ## subtracts ``interval`` time from TimeInfo ``a``. - ## - ## **Note:** This has been only briefly tested, it is inaccurate especially - ## when you subtract so much that you reach the Julian calendar. - let - t = toSeconds(toTime(a)) - secs = toSeconds(a, -interval) - if a.timezone == 0: - result = getGMTime(fromSeconds(t + secs)) - else: - result = getLocalTime(fromSeconds(t + secs)) - -proc miliseconds*(t: TimeInterval): int {.deprecated.} = t.milliseconds - -proc `miliseconds=`*(t: var TimeInterval, milliseconds: int) {.deprecated.} = - ## An alias for a misspelled field in ``TimeInterval``. - ## - ## **Warning:** This should not be used! It will be removed in the next - ## version. - t.milliseconds = milliseconds +proc `-`*(dt: DateTime, interval: TimeInterval): DateTime = + ## Subtract ``interval`` from ``dt``. Components from ``interval`` are subtracted + ## in the order of their size, i.e first the ``years`` component, then the ``months`` + ## component and so on. The returned ``DateTime`` will have the same timezone as the input. + dt + (-interval) proc getDateStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} = - ## gets the current date as a string of the format ``YYYY-MM-DD``. - var ti = getLocalTime(getTime()) - result = $ti.year & '-' & intToStr(ord(ti.month)+1, 2) & + ## Gets the current date as a string of the format ``YYYY-MM-DD``. + var ti = now() + result = $ti.year & '-' & intToStr(ord(ti.month), 2) & '-' & intToStr(ti.monthday, 2) proc getClockStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} = - ## gets the current clock time as a string of the format ``HH:MM:SS``. - var ti = getLocalTime(getTime()) + ## Gets the current clock time as a string of the format ``HH:MM:SS``. + var ti = now() result = intToStr(ti.hour, 2) & ':' & intToStr(ti.minute, 2) & ':' & intToStr(ti.second, 2) proc `$`*(day: WeekDay): string = - ## stingify operator for ``WeekDay``. + ## Stringify operator for ``WeekDay``. const lookup: array[WeekDay, string] = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] return lookup[day] proc `$`*(m: Month): string = - ## stingify operator for ``Month``. + ## Stringify operator for ``Month``. const lookup: array[Month, string] = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] @@ -450,74 +667,68 @@ proc `$`*(m: Month): string = proc milliseconds*(ms: int): TimeInterval {.inline.} = ## TimeInterval of `ms` milliseconds ## - ## Note: not all time functions have millisecond resolution - initInterval(`mod`(ms,1000), `div`(ms,1000)) + ## Note: not all time procedures have millisecond resolution + initInterval(milliseconds = ms) proc seconds*(s: int): TimeInterval {.inline.} = ## TimeInterval of `s` seconds ## ## ``echo getTime() + 5.second`` - initInterval(0,`mod`(s,60), `div`(s,60)) + initInterval(seconds = s) proc minutes*(m: int): TimeInterval {.inline.} = ## TimeInterval of `m` minutes ## ## ``echo getTime() + 5.minutes`` - initInterval(0,0,`mod`(m,60), `div`(m,60)) + initInterval(minutes = m) proc hours*(h: int): TimeInterval {.inline.} = ## TimeInterval of `h` hours ## ## ``echo getTime() + 2.hours`` - initInterval(0,0,0,`mod`(h,24),`div`(h,24)) + initInterval(hours = h) proc days*(d: int): TimeInterval {.inline.} = ## TimeInterval of `d` days ## ## ``echo getTime() + 2.days`` - initInterval(0,0,0,0,d) + initInterval(days = d) proc months*(m: int): TimeInterval {.inline.} = ## TimeInterval of `m` months ## ## ``echo getTime() + 2.months`` - initInterval(0,0,0,0,0,`mod`(m,12),`div`(m,12)) + initInterval(months = m) proc years*(y: int): TimeInterval {.inline.} = ## TimeInterval of `y` years ## ## ``echo getTime() + 2.years`` - initInterval(0,0,0,0,0,0,y) + initInterval(years = y) -proc `+=`*(t: var Time, ti: TimeInterval) = - ## modifies `t` by adding the interval `ti` - t = toTime(getLocalTime(t) + ti) +proc `+=`*(time: var Time, interval: TimeInterval) = + ## Modifies `time` by adding `interval`. + time = toTime(time.local + interval) -proc `+`*(t: Time, ti: TimeInterval): Time = - ## adds the interval `ti` to Time `t` - ## by converting to localTime, adding the interval, and converting back +proc `+`*(time: Time, interval: TimeInterval): Time = + ## Adds `interval` to `time` + ## by converting to a ``DateTime`` in the local timezone, + ## adding the interval, and converting back to ``Time``. ## ## ``echo getTime() + 1.day`` - result = toTime(getLocalTime(t) + ti) + result = toTime(time.local + interval) -proc `-=`*(t: var Time, ti: TimeInterval) = - ## modifies `t` by subtracting the interval `ti` - t = toTime(getLocalTime(t) - ti) +proc `-=`*(time: var Time, interval: TimeInterval) = + ## Modifies `time` by subtracting `interval`. + time = toTime(time.local - interval) -proc `-`*(t: Time, ti: TimeInterval): Time = - ## subtracts the interval `ti` from Time `t` +proc `-`*(time: Time, interval: TimeInterval): Time = + ## Subtracts `interval` from Time `time`. ## ## ``echo getTime() - 1.day`` - result = toTime(getLocalTime(t) - ti) + result = toTime(time.local - interval) -const - secondsInMin = 60 - secondsInHour = 60*60 - secondsInDay = 60*60*24 - minutesInHour = 60 - epochStartYear = 1970 - -proc formatToken(info: TimeInfo, token: string, buf: var string) = +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 @@ -525,96 +736,96 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) = ## formatting tokens require modifying the previous characters. case token of "d": - buf.add($info.monthday) + buf.add($dt.monthday) of "dd": - if info.monthday < 10: + if dt.monthday < 10: buf.add("0") - buf.add($info.monthday) + buf.add($dt.monthday) of "ddd": - buf.add(($info.weekday)[0 .. 2]) + buf.add(($dt.weekday)[0 .. 2]) of "dddd": - buf.add($info.weekday) + buf.add($dt.weekday) of "h": - buf.add($(if info.hour > 12: info.hour - 12 else: info.hour)) + buf.add($(if dt.hour > 12: dt.hour - 12 else: dt.hour)) of "hh": - let amerHour = if info.hour > 12: info.hour - 12 else: info.hour + 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($info.hour) + buf.add($dt.hour) of "HH": - if info.hour < 10: + if dt.hour < 10: buf.add('0') - buf.add($info.hour) + buf.add($dt.hour) of "m": - buf.add($info.minute) + buf.add($dt.minute) of "mm": - if info.minute < 10: + if dt.minute < 10: buf.add('0') - buf.add($info.minute) + buf.add($dt.minute) of "M": - buf.add($(int(info.month)+1)) + buf.add($ord(dt.month)) of "MM": - if info.month < mOct: + if dt.month < mOct: buf.add('0') - buf.add($(int(info.month)+1)) + buf.add($ord(dt.month)) of "MMM": - buf.add(($info.month)[0..2]) + buf.add(($dt.month)[0..2]) of "MMMM": - buf.add($info.month) + buf.add($dt.month) of "s": - buf.add($info.second) + buf.add($dt.second) of "ss": - if info.second < 10: + if dt.second < 10: buf.add('0') - buf.add($info.second) + buf.add($dt.second) of "t": - if info.hour >= 12: + if dt.hour >= 12: buf.add('P') else: buf.add('A') of "tt": - if info.hour >= 12: + if dt.hour >= 12: buf.add("PM") else: buf.add("AM") of "y": - var fr = ($info.year).len()-1 + var fr = ($dt.year).len()-1 if fr < 0: fr = 0 - buf.add(($info.year)[fr .. ($info.year).len()-1]) + buf.add(($dt.year)[fr .. ($dt.year).len()-1]) of "yy": - var fr = ($info.year).len()-2 + var fr = ($dt.year).len()-2 if fr < 0: fr = 0 - var fyear = ($info.year)[fr .. ($info.year).len()-1] + 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 = ($info.year).len()-3 + var fr = ($dt.year).len()-3 if fr < 0: fr = 0 - var fyear = ($info.year)[fr .. ($info.year).len()-1] + 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 = ($info.year).len()-4 + var fr = ($dt.year).len()-4 if fr < 0: fr = 0 - var fyear = ($info.year)[fr .. ($info.year).len()-1] + 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 = ($info.year).len()-5 + var fr = ($dt.year).len()-5 if fr < 0: fr = 0 - var fyear = ($info.year)[fr .. ($info.year).len()-1] + 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 = info.timezone - int(info.isDst) * secondsInHour + nonDstTz = dt.utcOffset hours = abs(nonDstTz) div secondsInHour if nonDstTz <= 0: buf.add('+') else: buf.add('-') buf.add($hours) of "zz": let - nonDstTz = info.timezone - int(info.isDst) * secondsInHour + nonDstTz = dt.utcOffset hours = abs(nonDstTz) div secondsInHour if nonDstTz <= 0: buf.add('+') else: buf.add('-') @@ -622,7 +833,7 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) = buf.add($hours) of "zzz": let - nonDstTz = info.timezone - int(info.isDst) * secondsInHour + nonDstTz = dt.utcOffset hours = abs(nonDstTz) div secondsInHour minutes = (abs(nonDstTz) div secondsInMin) mod minutesInHour if nonDstTz <= 0: buf.add('+') @@ -638,8 +849,8 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) = raise newException(ValueError, "Invalid format string: " & token) -proc format*(info: TimeInfo, f: string): string = - ## This function formats `info` as specified by `f`. The following format +proc format*(dt: DateTime, f: string): string {.tags: [].}= + ## This procedure formats `dt` as specified by `f`. The following format ## specifiers are available: ## ## ========== ================================================================================= ================================================ @@ -683,7 +894,7 @@ proc format*(info: TimeInfo, f: string): string = while true: case f[i] of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': - formatToken(info, currentF, result) + formatToken(dt, currentF, result) currentF = "" if f[i] == '\0': break @@ -700,187 +911,187 @@ proc format*(info: TimeInfo, f: string): string = if currentF.len < 1 or currentF[high(currentF)] == f[i]: currentF.add(f[i]) else: - formatToken(info, currentF, result) + formatToken(dt, currentF, result) dec(i) # Move position back to re-process the character separately. currentF = "" inc(i) -proc `$`*(timeInfo: TimeInfo): string {.tags: [], raises: [], benign.} = - ## converts a `TimeInfo` object to a string representation. +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``. try: - result = format(timeInfo, "yyyy-MM-dd'T'HH:mm:sszzz") # todo: optimize this + result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz") # todo: optimize this except ValueError: assert false # cannot happen because format string is valid -proc `$`*(time: Time): string {.tags: [TimeEffect], raises: [], benign.} = +proc `$`*(time: Time): string {.tags: [], raises: [], benign.} = ## converts a `Time` value to a string representation. It will use the local ## time zone and use the format ``yyyy-MM-dd'T'HH-mm-sszzz``. - $getLocalTime(time) + $time.local {.pop.} -proc parseToken(info: var TimeInfo; token, value: string; j: var int) = +proc parseToken(dt: var DateTime; token, value: string; j: var int) = ## Helper of the parse proc to parse individual tokens. var sv: int case token of "d": var pd = parseInt(value[j..j+1], sv) - info.monthday = sv + dt.monthday = sv j += pd of "dd": - info.monthday = value[j..j+1].parseInt() + dt.monthday = value[j..j+1].parseInt() j += 2 of "ddd": case value[j..j+2].toLowerAscii() - of "sun": info.weekday = dSun - of "mon": info.weekday = dMon - of "tue": info.weekday = dTue - of "wed": info.weekday = dWed - of "thu": info.weekday = dThu - of "fri": info.weekday = dFri - of "sat": info.weekday = dSat + 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: - info.weekday = dSun + dt.weekday = dSun j += 6 elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("monday") == 0: - info.weekday = dMon + dt.weekday = dMon j += 6 elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("tuesday") == 0: - info.weekday = dTue + dt.weekday = dTue j += 7 elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("wednesday") == 0: - info.weekday = dWed + dt.weekday = dWed j += 9 elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("thursday") == 0: - info.weekday = dThu + dt.weekday = dThu j += 8 elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("friday") == 0: - info.weekday = dFri + dt.weekday = dFri j += 6 elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("saturday") == 0: - info.weekday = dSat + 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) - info.hour = sv + dt.hour = sv j += pd of "hh", "HH": - info.hour = value[j..j+1].parseInt() + dt.hour = value[j..j+1].parseInt() j += 2 of "m": var pd = parseInt(value[j..j+1], sv) - info.minute = sv + dt.minute = sv j += pd of "mm": - info.minute = value[j..j+1].parseInt() + dt.minute = value[j..j+1].parseInt() j += 2 of "M": var pd = parseInt(value[j..j+1], sv) - info.month = Month(sv-1) + dt.month = sv.Month j += pd of "MM": var month = value[j..j+1].parseInt() j += 2 - info.month = Month(month-1) + dt.month = month.Month of "MMM": case value[j..j+2].toLowerAscii(): - of "jan": info.month = mJan - of "feb": info.month = mFeb - of "mar": info.month = mMar - of "apr": info.month = mApr - of "may": info.month = mMay - of "jun": info.month = mJun - of "jul": info.month = mJul - of "aug": info.month = mAug - of "sep": info.month = mSep - of "oct": info.month = mOct - of "nov": info.month = mNov - of "dec": info.month = mDec + 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: - info.month = mJan + dt.month = mJan j += 7 elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("february") == 0: - info.month = mFeb + dt.month = mFeb j += 8 elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("march") == 0: - info.month = mMar + dt.month = mMar j += 5 elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("april") == 0: - info.month = mApr + dt.month = mApr j += 5 elif value.len >= j+3 and value[j..j+2].cmpIgnoreCase("may") == 0: - info.month = mMay + dt.month = mMay j += 3 elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("june") == 0: - info.month = mJun + dt.month = mJun j += 4 elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("july") == 0: - info.month = mJul + dt.month = mJul j += 4 elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("august") == 0: - info.month = mAug + dt.month = mAug j += 6 elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("september") == 0: - info.month = mSep + dt.month = mSep j += 9 elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("october") == 0: - info.month = mOct + dt.month = mOct j += 7 elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("november") == 0: - info.month = mNov + dt.month = mNov j += 8 elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("december") == 0: - info.month = mDec + 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) - info.second = sv + dt.second = sv j += pd of "ss": - info.second = value[j..j+1].parseInt() + dt.second = value[j..j+1].parseInt() j += 2 of "t": - if value[j] == 'P' and info.hour > 0 and info.hour < 12: - info.hour += 12 + if value[j] == 'P' and dt.hour > 0 and dt.hour < 12: + dt.hour += 12 j += 1 of "tt": - if value[j..j+1] == "PM" and info.hour > 0 and info.hour < 12: - info.hour += 12 + if 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 = getLocalTime(getTime()).year div 100 - info.year = thisCen*100 + year + var thisCen = now().year div 100 + dt.year = thisCen*100 + year j += 2 of "yyyy": - info.year = value[j..j+3].parseInt() + dt.year = value[j..j+3].parseInt() j += 4 of "z": - info.isDST = false + dt.isDst = false if value[j] == '+': - info.timezone = 0 - parseInt($value[j+1]) * secondsInHour + dt.utcOffset = 0 - parseInt($value[j+1]) * secondsInHour elif value[j] == '-': - info.timezone = parseInt($value[j+1]) * secondsInHour + dt.utcOffset = parseInt($value[j+1]) * secondsInHour elif value[j] == 'Z': - info.timezone = 0 + dt.utcOffset = 0 j += 1 return else: @@ -888,13 +1099,13 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = "Couldn't parse timezone offset (z), got: " & value[j]) j += 2 of "zz": - info.isDST = false + dt.isDst = false if value[j] == '+': - info.timezone = 0 - value[j+1..j+2].parseInt() * secondsInHour + dt.utcOffset = 0 - value[j+1..j+2].parseInt() * secondsInHour elif value[j] == '-': - info.timezone = value[j+1..j+2].parseInt() * secondsInHour + dt.utcOffset = value[j+1..j+2].parseInt() * secondsInHour elif value[j] == 'Z': - info.timezone = 0 + dt.utcOffset = 0 j += 1 return else: @@ -902,31 +1113,33 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = "Couldn't parse timezone offset (zz), got: " & value[j]) j += 3 of "zzz": - info.isDST = false + dt.isDst = false var factor = 0 if value[j] == '+': factor = -1 elif value[j] == '-': factor = 1 elif value[j] == 'Z': - info.timezone = 0 + dt.utcOffset = 0 j += 1 return else: raise newException(ValueError, "Couldn't parse timezone offset (zzz), got: " & value[j]) - info.timezone = factor * value[j+1..j+2].parseInt() * secondsInHour + dt.utcOffset = factor * value[j+1..j+2].parseInt() * secondsInHour j += 4 - info.timezone += factor * value[j..j+1].parseInt() * 60 + dt.utcOffset += factor * value[j..j+1].parseInt() * 60 j += 2 else: # Ignore the token and move forward in the value string by the same length j += token.len -proc parse*(value, layout: string): TimeInfo = - ## This function parses a date/time string using the standard format - ## identifiers as listed below. The function defaults information not provided - ## in the format string from the running program (timezone, month, year, etc). - ## Daylight saving time is only set if no timezone is given and the given date - ## lies within the DST period of the current locale. +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 @@ -965,17 +1178,17 @@ proc parse*(value, layout: string): TimeInfo = 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 info = getLocalTime(getTime()) - info.hour = 0 - info.minute = 0 - info.second = 0 - info.isDST = true # using this is flag for checking whether a timezone has \ + var dt = now() + dt.hour = 0 + dt.minute = 0 + dt.second = 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 true: case layout[i] of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': if token.len > 0: - parseToken(info, token, value, j) + parseToken(dt, token, value, j) # Reset token token = "" # Break if at end of line @@ -997,26 +1210,15 @@ proc parse*(value, layout: string): TimeInfo = token.add(layout[i]) inc(i) else: - parseToken(info, token, value, j) + parseToken(dt, token, value, j) token = "" - if info.isDST: - # means that no timezone has been parsed. In this case, we need to check - # whether the date is within DST of the local time. - let tmp = getLocalTime(toTime(info)) - # correctly set isDST so that the following step works on the correct time - info.isDST = tmp.isDST - - # Correct weekday and yearday; transform timestamp to local time. - # There currently is no way of returning this with the original (parsed) - # timezone while also setting weekday and yearday (we are depending on stdlib - # to provide this calculation). - return getLocalTime(toTime(info)) - -# Leap year calculations are adapted from: -# http://www.codeproject.com/Articles/7358/Ultra-fast-Algorithms-for-Working-with-Leap-Years -# The dayOfTheWeek procs are adapated from: -# http://stason.org/TULARC/society/calendars/2-5-What-day-of-the-week-was-2-August-1953.html + 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 countLeapYears*(yearSpan: int): int = ## Returns the number of leap years spanned by a given number of years. @@ -1042,35 +1244,201 @@ proc countYearsAndDays*(daySpan: int): tuple[years: int, days: int] = result.years = days div 365 result.days = days mod 365 -proc getDayOfWeek*(day, month, year: int): WeekDay = - ## Returns the day of the week enum from day, month and year. - # Day & month start from one. - let - a = (14 - month) div 12 - y = year - a - m = month + (12*a) - 2 - d = (day + y + (y div 4) - (y div 100) + (y div 400) + (31*m) div 12) mod 7 - # The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc. - # so we must correct for the WeekDay type. - if d == 0: return dSun - result = (d-1).WeekDay +proc toTimeInterval*(time: Time): TimeInterval = + ## Converts a Time to a TimeInterval. + ## + ## To be used when diffing times. + ## + ## .. code-block:: nim + ## let a = fromSeconds(1_000_000_000) + ## let b = fromSeconds(1_500_000_000) + ## echo a, " ", b # real dates + ## echo a.toTimeInterval # meaningless value, don't use it by itself + ## echo b.toTimeInterval - a.toTimeInterval + ## # (milliseconds: 0, seconds: -40, minutes: -6, hours: 1, days: 5, months: -2, years: 16) + # Milliseconds not available from Time + var dt = time.local + initInterval(0, dt.second, dt.minute, dt.hour, dt.monthday, dt.month.ord - 1, dt.year) -proc getDayOfWeekJulian*(day, month, year: int): WeekDay = - ## Returns the day of the week enum from day, month and year, - ## according to the Julian calendar. - # Day & month start from one. - let - a = (14 - month) div 12 - y = year - a - m = month + (12*a) - 2 - d = (5 + day + y + (y div 4) + (31*m) div 12) mod 7 - result = d.WeekDay +proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, + hour: HourRange, minute: MinuteRange, second: SecondRange, zone: Timezone = local()): DateTime = + ## Create a new ``DateTime`` in the specified timezone. + assertValidDate monthday, month, year + doAssert monthday <= getDaysInMonth(month, year), "Invalid date: " & $month & " " & $monthday & ", " & $year + let dt = DateTime( + monthday: monthday, + year: year, + month: month, + hour: hour, + minute: minute, + second: second + ) + result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone) -proc timeToTimeInfo*(t: Time): TimeInfo {.deprecated.} = - ## Converts a Time to TimeInfo. +when not defined(JS): + proc epochTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} + ## gets time after the UNIX epoch (1970) in seconds. It is a float + ## because sub-second resolution is likely to be supported (depending + ## on the hardware/OS). + + proc cpuTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} + ## gets time spent that the CPU spent to run the current process in + ## seconds. This may be more useful for benchmarking than ``epochTime``. + ## However, it may measure the real time instead (depending on the OS). + ## The value of the result has no meaning. + ## To generate useful timing values, take the difference between + ## the results of two ``cpuTime`` calls: + ## + ## .. code-block:: nim + ## var t0 = cpuTime() + ## doWork() + ## echo "CPU time [s] ", cpuTime() - t0 + +when defined(JS): + proc getTime(): Time = + (newDate().getTime() div 1000).Time + + proc epochTime*(): float {.tags: [TimeEffect].} = + newDate().getTime() / 1000 + +else: + type + Clock {.importc: "clock_t".} = distinct int + + proc timec(timer: ptr CTime): CTime {. + importc: "time", header: "", tags: [].} + + proc getClock(): Clock {.importc: "clock", header: "", tags: [TimeEffect].} + + var + clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl.}: int + + proc getTime(): Time = + timec(nil).Time + + const + epochDiff = 116444736000000000'i64 + rateDiff = 10000000'i64 # 100 nsecs + + proc unixTimeToWinTime*(time: CTime): int64 = + ## converts a UNIX `Time` (``time_t``) to a Windows file time + result = int64(time) * rateDiff + epochDiff + + proc winTimeToUnixTime*(time: int64): CTime = + ## converts a Windows time to a UNIX `Time` (``time_t``) + result = CTime((time - epochDiff) div rateDiff) + + when not defined(useNimRtl): + proc epochTime(): float = + when defined(posix): + var a: Timeval + posix_gettimeofday(a) + result = toFloat(a.tv_sec) + toFloat(a.tv_usec)*0.00_0001 + elif defined(windows): + var f: winlean.FILETIME + getSystemTimeAsFileTime(f) + var i64 = rdFileTime(f) - epochDiff + var secs = i64 div rateDiff + var subsecs = i64 mod rateDiff + result = toFloat(int(secs)) + toFloat(int(subsecs)) * 0.0000001 + else: + {.error: "unknown OS".} + + proc cpuTime(): float = + result = toFloat(int(getClock())) / toFloat(clocksPerSec) + +# Deprecated procs + +proc fromSeconds*(since1970: float): Time {.tags: [], raises: [], benign, deprecated.} = + ## Takes a float which contains the number of seconds since the unix epoch and + ## returns a time object. + ## + ## **Deprecated since v0.18.0:** use ``fromUnix`` instead + Time(since1970) + +proc fromSeconds*(since1970: int64): Time {.tags: [], raises: [], benign, deprecated.} = + ## Takes an int which contains the number of seconds since the unix epoch and + ## returns a time object. + ## + ## **Deprecated since v0.18.0:** use ``fromUnix`` instead + Time(since1970) + +proc toSeconds*(time: Time): float {.tags: [], raises: [], benign, deprecated.} = + ## Returns the time in seconds since the unix epoch. + ## + ## **Deprecated since v0.18.0:** use ``toUnix`` instead + float(time) + +proc getLocalTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} = + ## Converts the calendar time `time` to broken-time representation, + ## expressed relative to the user's specified time zone. + ## + ## **Deprecated since v0.18.0:** use ``local`` instead + time.local + +proc getGMTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} = + ## Converts the calendar time `time` to broken-down time representation, + ## expressed in Coordinated Universal Time (UTC). + ## + ## **Deprecated since v0.18.0:** use ``utc`` instead + time.utc + +proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign, deprecated.} = + ## Returns the offset of the local (non-DST) timezone in seconds west of UTC. + ## + ## **Deprecated since v0.18.0:** use ``now().utcOffset`` to get the current + ## utc offset (including DST). + when defined(JS): + return newDate().getTimezoneOffset() * 60 + elif defined(freebsd) or defined(netbsd) or defined(openbsd): + var a = timec(nil) + let lt = localtime(addr(a)) + # BSD stores in `gmtoff` offset east of UTC in seconds, + # but posix systems using west of UTC in seconds + return -(lt.gmtoff) + else: + return timezone + +proc timeInfoToTime*(dt: DateTime): Time {.tags: [], benign, deprecated.} = + ## Converts a broken-down time structure to calendar time representation. ## ## **Warning:** This procedure is deprecated since version 0.14.0. - ## Use ``getLocalTime`` or ``getGMTime`` instead. + ## Use ``toTime`` instead. + dt.toTime + +when defined(JS): + var startMilsecs = getTime() + proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} = + ## get the milliseconds from the start of the program. **Deprecated since + ## version 0.8.10.** Use ``epochTime`` or ``cpuTime`` instead. + when defined(JS): + ## get the milliseconds from the start of the program + return int(getTime() - startMilsecs) +else: + proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} = + when defined(macosx): + result = toInt(toFloat(int(getClock())) / (toFloat(clocksPerSec) / 1000.0)) + else: + result = int(getClock()) div (clocksPerSec div 1000) + +proc miliseconds*(t: TimeInterval): int {.deprecated.} = + t.milliseconds + +proc timeToTimeInterval*(t: Time): TimeInterval {.deprecated.} = + ## Converts a Time to a TimeInterval. + ## + ## **Warning:** This procedure is deprecated since version 0.14.0. + ## Use ``toTimeInterval`` instead. + # Milliseconds not available from Time + t.toTimeInterval() + +proc timeToTimeInfo*(t: Time): DateTime {.deprecated.} = + ## Converts a Time to DateTime. + ## + ## **Warning:** This procedure is deprecated since version 0.14.0. + ## Use ``inZone`` instead. + const epochStartYear = 1970 + let secs = t.toSeconds().int daysSinceEpoch = secs div secondsInDay @@ -1095,307 +1463,22 @@ proc timeToTimeInfo*(t: Time): TimeInfo {.deprecated.} = m = mon # month is zero indexed enum md = days # NB: month is zero indexed but dayOfWeek expects 1 indexed. - wd = getDayOfWeek(days, mon.int + 1, y).Weekday + wd = getDayOfWeek(days, mon, y).Weekday h = daySeconds div secondsInHour + 1 mi = (daySeconds mod secondsInHour) div secondsInMin s = daySeconds mod secondsInMin - result = TimeInfo(year: y, yearday: yd, month: m, monthday: md, weekday: wd, hour: h, minute: mi, second: s) + result = DateTime(year: y, yearday: yd, month: m, monthday: md, weekday: wd, hour: h, minute: mi, second: s) -proc timeToTimeInterval*(t: Time): TimeInterval {.deprecated.} = - ## Converts a Time to a TimeInterval. - ## - ## **Warning:** This procedure is deprecated since version 0.14.0. - ## Use ``toTimeInterval`` instead. - # Milliseconds not available from Time - var tInfo = t.getLocalTime() - initInterval(0, tInfo.second, tInfo.minute, tInfo.hour, tInfo.weekday.ord, tInfo.month.ord, tInfo.year) +proc getDayOfWeek*(day, month, year: int): WeekDay {.tags: [], raises: [], benign, deprecated.} = + getDayOfWeek(day, month.Month, year) -proc toTimeInterval*(t: Time): TimeInterval = - ## Converts a Time to a TimeInterval. - ## - ## To be used when diffing times. - ## - ## .. code-block:: nim - ## let a = fromSeconds(1_000_000_000) - ## let b = fromSeconds(1_500_000_000) - ## echo a, " ", b # real dates - ## echo a.toTimeInterval # meaningless value, don't use it by itself - ## echo b.toTimeInterval - a.toTimeInterval - ## # (milliseconds: 0, seconds: -40, minutes: -6, hours: 1, days: -2, months: -2, years: 16) - # Milliseconds not available from Time - var tInfo = t.getLocalTime() - initInterval(0, tInfo.second, tInfo.minute, tInfo.hour, tInfo.weekday.ord, tInfo.month.ord, tInfo.year) - -when not defined(JS): - proc epochTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} - ## gets time after the UNIX epoch (1970) in seconds. It is a float - ## because sub-second resolution is likely to be supported (depending - ## on the hardware/OS). - - proc cpuTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} - ## gets time spent that the CPU spent to run the current process in - ## seconds. This may be more useful for benchmarking than ``epochTime``. - ## However, it may measure the real time instead (depending on the OS). - ## The value of the result has no meaning. - ## To generate useful timing values, take the difference between - ## the results of two ``cpuTime`` calls: - ## - ## .. code-block:: nim - ## var t0 = cpuTime() - ## doWork() - ## echo "CPU time [s] ", cpuTime() - t0 - -when not defined(JS): - # C wrapper: - when defined(freebsd) or defined(netbsd) or defined(openbsd) or - defined(macosx): - type - StructTM {.importc: "struct tm".} = object - second {.importc: "tm_sec".}, - minute {.importc: "tm_min".}, - hour {.importc: "tm_hour".}, - monthday {.importc: "tm_mday".}, - month {.importc: "tm_mon".}, - year {.importc: "tm_year".}, - weekday {.importc: "tm_wday".}, - yearday {.importc: "tm_yday".}, - isdst {.importc: "tm_isdst".}: cint - gmtoff {.importc: "tm_gmtoff".}: clong - else: - type - StructTM {.importc: "struct tm".} = object - second {.importc: "tm_sec".}, - minute {.importc: "tm_min".}, - hour {.importc: "tm_hour".}, - monthday {.importc: "tm_mday".}, - month {.importc: "tm_mon".}, - year {.importc: "tm_year".}, - weekday {.importc: "tm_wday".}, - yearday {.importc: "tm_yday".}, - isdst {.importc: "tm_isdst".}: cint - when defined(linux) and defined(amd64): - gmtoff {.importc: "tm_gmtoff".}: clong - zone {.importc: "tm_zone".}: cstring - type - TimeInfoPtr = ptr StructTM - Clock {.importc: "clock_t".} = distinct int - - when not defined(windows): - # This is not ANSI C, but common enough - proc timegm(t: StructTM): Time {. - importc: "timegm", header: "", tags: [].} - - proc localtime(timer: ptr Time): TimeInfoPtr {. - importc: "localtime", header: "", tags: [].} - proc gmtime(timer: ptr Time): TimeInfoPtr {. - importc: "gmtime", header: "", tags: [].} - proc timec(timer: ptr Time): Time {. - importc: "time", header: "", tags: [].} - proc mktime(t: StructTM): Time {. - importc: "mktime", header: "", tags: [].} - proc getClock(): Clock {.importc: "clock", header: "", tags: [TimeEffect].} - proc difftime(a, b: Time): float {.importc: "difftime", header: "", - tags: [].} - - var - clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl.}: int - - # our own procs on top of that: - proc tmToTimeInfo(tm: StructTM, local: bool): TimeInfo = - const - weekDays: array[0..6, WeekDay] = [ - dSun, dMon, dTue, dWed, dThu, dFri, dSat] - TimeInfo(second: int(tm.second), - minute: int(tm.minute), - hour: int(tm.hour), - monthday: int(tm.monthday), - month: Month(tm.month), - year: tm.year + 1900'i32, - weekday: weekDays[int(tm.weekday)], - yearday: int(tm.yearday), - isDST: tm.isdst > 0, - timezone: if local: getTimezone() else: 0 - ) - - - proc timeInfoToTM(t: TimeInfo): StructTM = - const - weekDays: array[WeekDay, int8] = [1'i8,2'i8,3'i8,4'i8,5'i8,6'i8,0'i8] - result.second = t.second - result.minute = t.minute - result.hour = t.hour - result.monthday = t.monthday - result.month = cint(t.month) - result.year = cint(t.year - 1900) - result.weekday = weekDays[t.weekday] - result.yearday = t.yearday - result.isdst = if t.isDST: 1 else: 0 - - when not defined(useNimRtl): - proc `-` (a, b: Time): int64 = - return toBiggestInt(difftime(a, b)) - - proc getStartMilsecs(): int = - #echo "clocks per sec: ", clocksPerSec, "clock: ", int(getClock()) - #return getClock() div (clocksPerSec div 1000) - when defined(macosx): - result = toInt(toFloat(int(getClock())) / (toFloat(clocksPerSec) / 1000.0)) - else: - result = int(getClock()) div (clocksPerSec div 1000) - when false: - var a: Timeval - posix_gettimeofday(a) - result = a.tv_sec * 1000'i64 + a.tv_usec div 1000'i64 - #echo "result: ", result - - proc getTime(): Time = return timec(nil) - proc getLocalTime(t: Time): TimeInfo = - var a = t - let lt = localtime(addr(a)) - assert(not lt.isNil) - result = tmToTimeInfo(lt[], true) - # copying is needed anyway to provide reentrancity; thus - # the conversion is not expensive - - proc getGMTime(t: Time): TimeInfo = - var a = t - result = tmToTimeInfo(gmtime(addr(a))[], false) - # copying is needed anyway to provide reentrancity; thus - # the conversion is not expensive - - proc toTime(timeInfo: TimeInfo): Time = - var cTimeInfo = timeInfo # for C++ we have to make a copy - # because the header of mktime is broken in my version of libc - - result = mktime(timeInfoToTM(cTimeInfo)) - # mktime is defined to interpret the input as local time. As timeInfoToTM - # does ignore the timezone, we need to adjust this here. - result = Time(TimeImpl(result) - getTimezone() + timeInfo.timezone) - - proc timeInfoToTime(timeInfo: TimeInfo): Time = toTime(timeInfo) - - const - epochDiff = 116444736000000000'i64 - rateDiff = 10000000'i64 # 100 nsecs - - proc unixTimeToWinTime*(t: Time): int64 = - ## converts a UNIX `Time` (``time_t``) to a Windows file time - result = int64(t) * rateDiff + epochDiff - - proc winTimeToUnixTime*(t: int64): Time = - ## converts a Windows time to a UNIX `Time` (``time_t``) - result = Time((t - epochDiff) div rateDiff) - - proc getTimezone(): int = - when defined(freebsd) or defined(netbsd) or defined(openbsd): - var a = timec(nil) - let lt = localtime(addr(a)) - # BSD stores in `gmtoff` offset east of UTC in seconds, - # but posix systems using west of UTC in seconds - return -(lt.gmtoff) - else: - return timezone - - proc fromSeconds(since1970: float): Time = Time(since1970) - - proc toSeconds(time: Time): float = float(time) - - when not defined(useNimRtl): - proc epochTime(): float = - when defined(posix): - var a: Timeval - posix_gettimeofday(a) - result = toFloat(a.tv_sec) + toFloat(a.tv_usec)*0.00_0001 - elif defined(windows): - var f: winlean.FILETIME - getSystemTimeAsFileTime(f) - var i64 = rdFileTime(f) - epochDiff - var secs = i64 div rateDiff - var subsecs = i64 mod rateDiff - result = toFloat(int(secs)) + toFloat(int(subsecs)) * 0.0000001 - else: - {.error: "unknown OS".} - - proc cpuTime(): float = - result = toFloat(int(getClock())) / toFloat(clocksPerSec) - -elif defined(JS): - proc newDate(): Time {.importc: "new Date".} - proc internGetTime(): Time {.importc: "new Date", tags: [].} - - proc newDate(value: float): Time {.importc: "new Date".} - proc newDate(value: cstring): Time {.importc: "new Date".} - proc getTime(): Time = - # Warning: This is something different in JS. - return newDate() - - const - weekDays: array[0..6, WeekDay] = [ - dSun, dMon, dTue, dWed, dThu, dFri, dSat] - - proc getLocalTime(t: Time): TimeInfo = - result.second = t.getSeconds() - result.minute = t.getMinutes() - result.hour = t.getHours() - result.monthday = t.getDate() - result.month = Month(t.getMonth()) - result.year = t.getFullYear() - result.weekday = weekDays[t.getDay()] - result.timezone = getTimezone() - - result.yearday = result.monthday - 1 - for month in mJan..`` exits with 0 or 1 ## ## Running a single test -## --------------------- +## ===================== ## -## Simply specify the test name as a command line argument. +## Specify the test name as a command line argument. ## ## .. code:: ## -## nim c -r test "my super awesome test name" +## nim c -r test "my test name" "another test" +## +## Multiple arguments can be used. +## +## Running a single test suite +## =========================== +## +## Specify the suite name delimited by ``"::"``. +## +## .. code:: +## +## nim c -r test "my test name::" +## +## Selecting tests by pattern +## ========================== +## +## A single ``"*"`` can be used for globbing. +## +## Delimit the end of a suite name with ``"::"``. +## +## Tests matching **any** of the arguments are executed. +## +## .. code:: +## +## nim c -r test fast_suite::mytest1 fast_suite::mytest2 +## nim c -r test "fast_suite::mytest*" +## nim c -r test "auth*::" "crypto::hashing*" +## # Run suites starting with 'bug #' and standalone tests starting with '#' +## nim c -r test 'bug #*::' '::#*' ## ## Example ## ------- @@ -121,7 +149,7 @@ var checkpoints {.threadvar.}: seq[string] formatters {.threadvar.}: seq[OutputFormatter] - testsToRun {.threadvar.}: HashSet[string] + testsFilters {.threadvar.}: HashSet[string] when declared(stdout): abortOnError = existsEnv("NIMTEST_ABORT_ON_ERROR") @@ -300,22 +328,63 @@ method testEnded*(formatter: JUnitOutputFormatter, testResult: TestResult) = method suiteEnded*(formatter: JUnitOutputFormatter) = formatter.stream.writeLine("\t") -proc shouldRun(testName: string): bool = - if testsToRun.len == 0: +proc glob(matcher, filter: string): bool = + ## Globbing using a single `*`. Empty `filter` matches everything. + if filter.len == 0: return true - result = testName in testsToRun + if not filter.contains('*'): + return matcher == filter + + let beforeAndAfter = filter.split('*', maxsplit=1) + if beforeAndAfter.len == 1: + # "foo*" + return matcher.startswith(beforeAndAfter[0]) + + if matcher.len < filter.len - 1: + return false # "12345" should not match "123*345" + + return matcher.startsWith(beforeAndAfter[0]) and matcher.endsWith(beforeAndAfter[1]) + +proc matchFilter(suiteName, testName, filter: string): bool = + if filter == "": + return true + if testName == filter: + # corner case for tests containing "::" in their name + return true + let suiteAndTestFilters = filter.split("::", maxsplit=1) + + if suiteAndTestFilters.len == 1: + # no suite specified + let test_f = suiteAndTestFilters[0] + return glob(testName, test_f) + + return glob(suiteName, suiteAndTestFilters[0]) and glob(testName, suiteAndTestFilters[1]) + +when defined(testing): export matchFilter + +proc shouldRun(currentSuiteName, testName: string): bool = + ## Check if a test should be run by matching suiteName and testName against + ## test filters. + if testsFilters.len == 0: + return true + + for f in testsFilters: + if matchFilter(currentSuiteName, testName, f): + return true + + return false proc ensureInitialized() = if formatters == nil: formatters = @[OutputFormatter(defaultConsoleFormatter())] - if not testsToRun.isValid: - testsToRun.init() + if not testsFilters.isValid: + testsFilters.init() when declared(paramCount): # Read tests to run from the command line. for i in 1 .. paramCount(): - testsToRun.incl(paramStr(i)) + testsFilters.incl(paramStr(i)) # These two procs are added as workarounds for # https://github.com/nim-lang/Nim/issues/5549 @@ -395,7 +464,7 @@ template test*(name, body) {.dirty.} = ensureInitialized() - if shouldRun(name): + if shouldRun(when declared(testSuiteName): testSuiteName else: "", name): checkpoints = @[] var testStatusIMPL {.inject.} = OK diff --git a/lib/system.nim b/lib/system.nim index b9f01c3065..8f83fb8c38 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -249,6 +249,10 @@ type when defined(nimHasOpt): type opt*{.magic: "Opt".}[T] +when defined(nimNewRuntime): + type sink*{.magic: "BuiltinType".}[T] + type lent*{.magic: "BuiltinType".}[T] + proc high*[T: Ordinal](x: T): T {.magic: "High", noSideEffect.} ## returns the highest possible index of an array, a sequence, a string or ## the highest possible value of an ordinal value `x`. As a special @@ -463,7 +467,7 @@ type line*: int ## line number of the proc that is currently executing filename*: cstring ## filename of the proc that is currently executing - Exception* {.compilerproc.} = object of RootObj ## \ + Exception* {.compilerproc, magic: "Exception".} = object of RootObj ## \ ## Base exception class. ## ## Each exception has to inherit from `Exception`. See the full `exception @@ -743,6 +747,18 @@ proc newSeqOfCap*[T](cap: Natural): seq[T] {. ## ``cap``. discard +when not defined(JS): + proc newSeqUninitialized*[T: SomeNumber](len: Natural): seq[T] = + ## creates a new sequence of type ``seq[T]`` with length ``len``. + ## + ## Only available for numbers types. Note that the sequence will be + ## uninitialized. After the creation of the sequence you should assign + ## entries to the sequence instead of adding them. + + result = newSeqOfCap[T](len) + var s = cast[PGenericSeq](result) + s.len = len + proc len*[TOpenArray: openArray|varargs](x: TOpenArray): int {. magic: "LengthOpenArray", noSideEffect.} proc len*(x: string): int {.magic: "LengthStr", noSideEffect.} @@ -1325,6 +1341,7 @@ proc add*(x: var string, y: string) {.magic: "AppendStrStr", noSideEffect.} ## tmp.add("cd") ## assert(tmp == "abcd") + type Endianness* = enum ## is a type describing the endianness of a processor. littleEndian, bigEndian @@ -2503,9 +2520,9 @@ proc `$`*[T: tuple|object](x: T): string = when compiles($value): when compiles(value.isNil): if value.isNil: result.add "nil" - else: result.add($value) + else: result.addQuoted(value) else: - result.add($value) + result.addQuoted(value) firstElement = false else: result.add("...") @@ -2525,12 +2542,9 @@ proc collectionToString[T](x: T, prefix, separator, suffix: string): string = if value.isNil: result.add "nil" else: - result.add($value) - # prevent temporary string allocation - elif compiles(result.add(value)): - result.add(value) + result.addQuoted(value) else: - result.add($value) + result.addQuoted(value) result.add(suffix) @@ -2918,7 +2932,10 @@ when not defined(JS): #and not defined(nimscript): elif x > y: result = 1 else: result = 0 else: - result = int(c_strcmp(x, y)) + let minlen = min(x.len, y.len) + result = int(c_memcmp(x.cstring, y.cstring, minlen.csize)) + if result == 0: + result = x.len - y.len when defined(nimscript): proc readFile*(filename: string): string {.tags: [ReadIOEffect], benign.} @@ -3893,6 +3910,65 @@ proc compiles*(x: untyped): bool {.magic: "Compiles", noSideEffect, compileTime. when declared(initDebugger): initDebugger() +proc addEscapedChar*(s: var string, c: char) {.noSideEffect, inline.} = + ## Adds a char to string `s` and applies the following escaping: + ## + ## * replaces any ``\`` by ``\\`` + ## * replaces any ``'`` by ``\'`` + ## * replaces any ``"`` by ``\"`` + ## * replaces any other character in the set ``{'\0'..'\31', '\127'..'\255'}`` + ## by ``\xHH`` where ``HH`` is its hexadecimal value. + ## + ## The procedure has been designed so that its output is usable for many + ## different common syntaxes. + ## **Note**: This is not correct for producing Ansi C code! + case c + of '\0'..'\31', '\127'..'\255': + add(s, "\\x") + const HexChars = "0123456789ABCDEF" + let n = ord(c) + s.add(HexChars[int((n and 0xF0) shr 4)]) + s.add(HexChars[int(n and 0xF)]) + of '\\': add(s, "\\\\") + of '\'': add(s, "\\'") + of '\"': add(s, "\\\"") + else: add(s, c) + +proc addQuoted*[T](s: var string, x: T) = + ## Appends `x` to string `s` in place, applying quoting and escaping + ## if `x` is a string or char. See + ## `addEscapedChar `_ + ## for the escaping scheme. + ## + ## The Nim standard library uses this function on the elements of + ## collections when producing a string representation of a collection. + ## It is recommended to use this function as well for user-side collections. + ## Users may overload `addQuoted` for custom (string-like) types if + ## they want to implement a customized element representation. + ## + ## .. code-block:: Nim + ## var tmp = "" + ## tmp.addQuoted(1) + ## tmp.add(", ") + ## tmp.addQuoted("string") + ## tmp.add(", ") + ## tmp.addQuoted('c') + ## assert(tmp == """1, "string", 'c'""") + when T is string: + s.add("\"") + for c in x: + s.addEscapedChar(c) + s.add("\"") + elif T is char: + s.add("'") + s.addEscapedChar(x) + s.add("'") + # prevent temporary string allocation + elif compiles(s.add(x)): + s.add(x) + else: + s.add($x) + when hasAlloc: # XXX: make these the default (or implement the NilObject optimization) proc safeAdd*[T](x: var seq[T], y: T) {.noSideEffect.} = @@ -4034,4 +4110,4 @@ template doAssertRaises*(exception, code: untyped): typed = except Exception as exc: raiseAssert(astToStr(exception) & " wasn't raised, another error was raised instead by:\n"& - astToStr(code)) \ No newline at end of file + astToStr(code)) diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index e274e8e0cd..46f75396b2 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -813,13 +813,13 @@ proc alloc0(allocator: var MemRegion, size: Natural): pointer = zeroMem(result, size) proc dealloc(allocator: var MemRegion, p: pointer) = - sysAssert(p != nil, "dealloc 0") + sysAssert(p != nil, "dealloc: p is nil") var x = cast[pointer](cast[ByteAddress](p) -% sizeof(FreeCell)) - sysAssert(x != nil, "dealloc 1") + sysAssert(x != nil, "dealloc: x is nil") sysAssert(isAccessible(allocator, x), "is not accessible") - sysAssert(cast[ptr FreeCell](x).zeroField == 1, "dealloc 2") + sysAssert(cast[ptr FreeCell](x).zeroField == 1, "dealloc: object header corrupted") rawDealloc(allocator, x) - sysAssert(not isAllocatedPtr(allocator, x), "dealloc 3") + sysAssert(not isAllocatedPtr(allocator, x), "dealloc: object still accessible") track("dealloc", p, 0) proc realloc(allocator: var MemRegion, p: pointer, newsize: Natural): pointer = diff --git a/lib/system/chcks.nim b/lib/system/chcks.nim index 1520f231e1..69b680dbdc 100644 --- a/lib/system/chcks.nim +++ b/lib/system/chcks.nim @@ -63,7 +63,6 @@ proc chckObj(obj, subclass: PNimType) {.compilerproc.} = while x != subclass: if x == nil: sysFatal(ObjectConversionError, "invalid object conversion") - break x = x.base proc chckObjAsgn(a, b: PNimType) {.compilerproc, inline.} = diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index 70c18ae21e..8e42ea4683 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -70,6 +70,18 @@ proc getFrame*(): PFrame {.compilerRtl, inl.} = framePtr proc popFrame {.compilerRtl, inl.} = framePtr = framePtr.prev +when false: + proc popFrameOfAddr(s: PFrame) {.compilerRtl.} = + var it = framePtr + if it == s: + framePtr = framePtr.prev + else: + while it != nil: + if it == s: + framePtr = it.prev + break + it = it.prev + proc setFrame*(s: PFrame) {.compilerRtl, inl.} = framePtr = s diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 68bf5f6c2d..66d49ce1bc 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -21,10 +21,6 @@ const # reaches this threshold # this seems to be a good value withRealTime = defined(useRealtimeGC) - useMarkForDebug = defined(gcGenerational) - useBackupGc = true # use a simple M&S GC to collect - # cycles instead of the complex - # algorithm when withRealTime and not declared(getTicks): include "system/timers" @@ -92,14 +88,12 @@ type maxPause: Nanos # max allowed pause in nanoseconds; active if > 0 region: MemRegion # garbage collected region stat: GcStat - when useMarkForDebug or useBackupGc: - marked: CellSet - additionalRoots: CellSeq # dummy roots for GC_ref/unref + marked: CellSet + additionalRoots: CellSeq # dummy roots for GC_ref/unref when hasThreadSupport: toDispose: SharedList[pointer] + gcThreadId: int -{.deprecated: [TWalkOp: WalkOp, TFinalizer: Finalizer, TGcHeap: GcHeap, - TGcStat: GcStat].} var gch {.rtlThreadVar.}: GcHeap @@ -165,12 +159,12 @@ when defined(logGC): if not c.typ.name.isNil: typName = c.typ.name - when leakDetector: - c_fprintf(stdout, "[GC] %s: %p %d %s rc=%ld from %s(%ld)\n", - msg, c, kind, typName, c.refcount shr rcShift, c.filename, c.line) - else: - c_fprintf(stdout, "[GC] %s: %p %d %s rc=%ld; color=%ld\n", - msg, c, kind, typName, c.refcount shr rcShift, c.color) + when leakDetector: + c_fprintf(stdout, "[GC] %s: %p %d %s rc=%ld from %s(%ld)\n", + msg, c, kind, typName, c.refcount shr rcShift, c.filename, c.line) + else: + c_fprintf(stdout, "[GC] %s: %p %d %s rc=%ld; thread=%ld\n", + msg, c, kind, typName, c.refcount shr rcShift, gch.gcThreadId) template gcTrace(cell, state: untyped) = when traceGC: traceCell(cell, state) @@ -314,27 +308,12 @@ proc initGC() = init(gch.zct) init(gch.tempStack) init(gch.decStack) - when useMarkForDebug or useBackupGc: - init(gch.marked) - init(gch.additionalRoots) + init(gch.marked) + init(gch.additionalRoots) when hasThreadSupport: - gch.toDispose = initSharedList[pointer]() - -when useMarkForDebug or useBackupGc: - type - GlobalMarkerProc = proc () {.nimcall, benign.} - {.deprecated: [TGlobalMarkerProc: GlobalMarkerProc].} - var - globalMarkersLen: int - globalMarkers: array[0.. 7_000, GlobalMarkerProc] - - proc nimRegisterGlobalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} = - if globalMarkersLen <= high(globalMarkers): - globalMarkers[globalMarkersLen] = markerProc - inc globalMarkersLen - else: - echo "[GC] cannot register global variable; too many global variables" - quit 1 + init(gch.toDispose) + gch.gcThreadId = atomicInc(gHeapidGenerator) - 1 + gcAssert(gch.gcThreadId >= 0, "invalid computed thread ID") proc cellsetReset(s: var CellSet) = deinit(s) @@ -377,10 +356,10 @@ proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) = else: discard proc forAllChildren(cell: PCell, op: WalkOp) = - gcAssert(cell != nil, "forAllChildren: 1") - gcAssert(isAllocatedPtr(gch.region, cell), "forAllChildren: 2") - gcAssert(cell.typ != nil, "forAllChildren: 3") - gcAssert cell.typ.kind in {tyRef, tyOptAsRef, tySequence, tyString}, "forAllChildren: 4" + gcAssert(cell != nil, "forAllChildren: cell is nil") + gcAssert(isAllocatedPtr(gch.region, cell), "forAllChildren: pointer not part of the heap") + gcAssert(cell.typ != nil, "forAllChildren: cell.typ is nil") + gcAssert cell.typ.kind in {tyRef, tyOptAsRef, tySequence, tyString}, "forAllChildren: unknown GC'ed type" let marker = cell.typ.marker if marker != nil: marker(cellToUsr(cell), op.int) @@ -481,7 +460,7 @@ proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer = release(gch) when useCellIds: inc gch.idGenerator - res.id = gch.idGenerator + res.id = gch.idGenerator * 1000_000 + gch.gcThreadId result = cellToUsr(res) sysAssert(allocInv(gch.region), "rawNewObj end") @@ -528,7 +507,7 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} = release(gch) when useCellIds: inc gch.idGenerator - res.id = gch.idGenerator + res.id = gch.idGenerator * 1000_000 + gch.gcThreadId result = cellToUsr(res) zeroMem(result, size) sysAssert(allocInv(gch.region), "newObjRC1 end") @@ -598,7 +577,7 @@ proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer = release(gch) when useCellIds: inc gch.idGenerator - res.id = gch.idGenerator + res.id = gch.idGenerator * 1000_000 + gch.gcThreadId result = cellToUsr(res) sysAssert(allocInv(gch.region), "growObj end") when defined(memProfiler): nimProfile(newsize-oldsize) @@ -623,29 +602,31 @@ proc freeCyclicCell(gch: var GcHeap, c: PCell) = gcAssert(c.typ != nil, "freeCyclicCell") zeroMem(c, sizeof(Cell)) -when useBackupGc: - proc sweep(gch: var GcHeap) = - for x in allObjects(gch.region): - if isCell(x): - # cast to PCell is correct here: - var c = cast[PCell](x) - if c notin gch.marked: freeCyclicCell(gch, c) +proc sweep(gch: var GcHeap) = + for x in allObjects(gch.region): + if isCell(x): + # cast to PCell is correct here: + var c = cast[PCell](x) + if c notin gch.marked: freeCyclicCell(gch, c) -when useMarkForDebug or useBackupGc: - proc markS(gch: var GcHeap, c: PCell) = - incl(gch.marked, c) - gcAssert gch.tempStack.len == 0, "stack not empty!" - forAllChildren(c, waMarkPrecise) - while gch.tempStack.len > 0: - dec gch.tempStack.len - var d = gch.tempStack.d[gch.tempStack.len] - if not containsOrIncl(gch.marked, d): - forAllChildren(d, waMarkPrecise) +proc markS(gch: var GcHeap, c: PCell) = + gcAssert isAllocatedPtr(gch.region, c), "markS: foreign heap root detected A!" + incl(gch.marked, c) + gcAssert gch.tempStack.len == 0, "stack not empty!" + forAllChildren(c, waMarkPrecise) + while gch.tempStack.len > 0: + dec gch.tempStack.len + var d = gch.tempStack.d[gch.tempStack.len] + gcAssert isAllocatedPtr(gch.region, d), "markS: foreign heap root detected B!" + if not containsOrIncl(gch.marked, d): + forAllChildren(d, waMarkPrecise) - proc markGlobals(gch: var GcHeap) = +proc markGlobals(gch: var GcHeap) = + if gch.gcThreadId == 0: for i in 0 .. globalMarkersLen-1: globalMarkers[i]() - let d = gch.additionalRoots.d - for i in 0 .. gch.additionalRoots.len-1: markS(gch, d[i]) + for i in 0 .. threadLocalMarkersLen-1: threadLocalMarkers[i]() + let d = gch.additionalRoots.d + for i in 0 .. gch.additionalRoots.len-1: markS(gch, d[i]) when logGC: var @@ -689,16 +670,9 @@ proc doOperation(p: pointer, op: WalkOp) = of waPush: add(gch.tempStack, c) of waMarkGlobal: - when useMarkForDebug or useBackupGc: - when hasThreadSupport: - # could point to a cell which we don't own and don't want to touch/trace - if isAllocatedPtr(gch.region, c): - markS(gch, c) - else: - markS(gch, c) + markS(gch, c) of waMarkPrecise: - when useMarkForDebug or useBackupGc: - add(gch.tempStack, c) + add(gch.tempStack, c) #of waDebug: debugGraph(c) proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} = @@ -712,14 +686,13 @@ proc collectCycles(gch: var GcHeap) = nimGCunref(c) # ensure the ZCT 'color' is not used: while gch.zct.len > 0: discard collectZCT(gch) - when useBackupGc: - cellsetReset(gch.marked) - var d = gch.decStack.d - for i in 0..gch.decStack.len-1: - sysAssert isAllocatedPtr(gch.region, d[i]), "collectCycles" - markS(gch, d[i]) - markGlobals(gch) - sweep(gch) + cellsetReset(gch.marked) + var d = gch.decStack.d + for i in 0..gch.decStack.len-1: + sysAssert isAllocatedPtr(gch.region, d[i]), "collectCycles" + markS(gch, d[i]) + markGlobals(gch) + sweep(gch) proc gcMark(gch: var GcHeap, p: pointer) {.inline.} = # the addresses are not as cells on the stack, so turn them to cells: @@ -860,7 +833,7 @@ proc collectCT(gch: var GcHeap) = if (gch.zct.len >= stackMarkCosts or (cycleGC and getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) and gch.recGcLock == 0: - when useMarkForDebug: + when false: prepareForInteriorPointerChecking(gch.region) cellsetReset(gch.marked) markForDebug(gch) diff --git a/lib/system/gc2.nim b/lib/system/gc2.nim index 4ecf3b2262..ca2a35f601 100644 --- a/lib/system/gc2.nim +++ b/lib/system/gc2.nim @@ -104,6 +104,7 @@ type pDumpHeapFile: pointer # File that is used for GC_dumpHeap when hasThreadSupport: toDispose: SharedList[pointer] + gcThreadId: int var gch {.rtlThreadVar.}: GcHeap @@ -119,22 +120,6 @@ template release(gch: GcHeap) = when hasThreadSupport and hasSharedHeap: releaseSys(HeapLock) -proc initGC() = - when not defined(useNimRtl): - gch.red = (1-gch.black) - gch.cycleThreshold = InitialCycleThreshold - gch.stat.stackScans = 0 - gch.stat.completedCollections = 0 - gch.stat.maxThreshold = 0 - gch.stat.maxStackSize = 0 - gch.stat.maxStackCells = 0 - gch.stat.cycleTableSize = 0 - # init the rt - init(gch.additionalRoots) - init(gch.greyStack) - when hasThreadSupport: - gch.toDispose = initSharedList[pointer]() - # Which color to use for new objects is tricky: When we're marking, # they have to be *white* so that everything is marked that is only # reachable from them. However, when we are sweeping, they have to @@ -284,20 +269,6 @@ proc unsureAsgnRef(dest: PPointer, src: pointer) {.compilerProc.} = if not isOnStack(dest): markGrey(s) dest[] = src -type - GlobalMarkerProc = proc () {.nimcall, benign.} -var - globalMarkersLen: int - globalMarkers: array[0.. 7_000, GlobalMarkerProc] - -proc nimRegisterGlobalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} = - if globalMarkersLen <= high(globalMarkers): - globalMarkers[globalMarkersLen] = markerProc - inc globalMarkersLen - else: - echo "[GC] cannot register global variable; too many global variables" - quit 1 - proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) {.benign.} = var d = cast[ByteAddress](dest) case n.kind @@ -354,6 +325,24 @@ proc gcInvariant*() = include gc_common +proc initGC() = + when not defined(useNimRtl): + gch.red = (1-gch.black) + gch.cycleThreshold = InitialCycleThreshold + gch.stat.stackScans = 0 + gch.stat.completedCollections = 0 + gch.stat.maxThreshold = 0 + gch.stat.maxStackSize = 0 + gch.stat.maxStackCells = 0 + gch.stat.cycleTableSize = 0 + # init the rt + init(gch.additionalRoots) + init(gch.greyStack) + when hasThreadSupport: + init(gch.toDispose) + gch.gcThreadId = atomicInc(gHeapidGenerator) - 1 + gcAssert(gch.gcThreadId >= 0, "invalid computed thread ID") + proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer = # generates a new object and sets its reference counter to 0 sysAssert(allocInv(gch.region), "rawNewObj begin") @@ -492,7 +481,9 @@ proc GC_dumpHeap*(file: File) = c_fprintf(file, "onstack %p\n", d[i]) else: c_fprintf(file, "onstack_invalid %p\n", d[i]) - for i in 0 .. globalMarkersLen-1: globalMarkers[i]() + if gch.gcThreadId == 0: + for i in 0 .. globalMarkersLen-1: globalMarkers[i]() + for i in 0 .. threadLocalMarkersLen-1: threadLocalMarkers[i]() while true: let x = allObjectsAsProc(gch.region, addr spaceIter) if spaceIter.state < 0: break @@ -579,7 +570,9 @@ proc markIncremental(gch: var GcHeap): bool = result = true proc markGlobals(gch: var GcHeap) = - for i in 0 .. globalMarkersLen-1: globalMarkers[i]() + if gch.gcThreadId == 0: + for i in 0 .. globalMarkersLen-1: globalMarkers[i]() + for i in 0 .. threadLocalMarkersLen-1: threadLocalMarkers[i]() proc doOperation(p: pointer, op: WalkOp) = if p == nil: return @@ -599,22 +592,14 @@ proc doOperation(p: pointer, op: WalkOp) = markRoot(gch, c) else: dumpRoot(gch, c) - when hasThreadSupport: - # could point to a cell which we don't own and don't want to touch/trace - if isAllocatedPtr(gch.region, c): handleRoot() - else: - #gcAssert(isAllocatedPtr(gch.region, c), "doOperation: waMarkGlobal") - if not isAllocatedPtr(gch.region, c): - c_fprintf(stdout, "[GC] not allocated anymore: MarkGlobal %p\n", c) - #GC_dumpHeap() - sysAssert(false, "wtf") - handleRoot() + handleRoot() discard allocInv(gch.region) of waMarkGrey: - if not isAllocatedPtr(gch.region, c): - c_fprintf(stdout, "[GC] not allocated anymore: MarkGrey %p\n", c) - #GC_dumpHeap() - sysAssert(false, "wtf") + when false: + if not isAllocatedPtr(gch.region, c): + c_fprintf(stdout, "[GC] not allocated anymore: MarkGrey %p\n", c) + #GC_dumpHeap() + sysAssert(false, "wtf") if c.color == 1-gch.black: c.setColor(rcGrey) add(gch.greyStack, c) diff --git a/lib/system/gc_common.nim b/lib/system/gc_common.nim index 484a4db9a0..ad37df0e0c 100644 --- a/lib/system/gc_common.nim +++ b/lib/system/gc_common.nim @@ -393,3 +393,28 @@ proc deallocHeap*(runFinalizers = true; allowGcAfterwards = true) = zeroMem(addr gch.region, sizeof(gch.region)) if allowGcAfterwards: initGC() + +type + GlobalMarkerProc = proc () {.nimcall, benign.} +var + globalMarkersLen: int + globalMarkers: array[0.. 3499, GlobalMarkerProc] + threadLocalMarkersLen: int + threadLocalMarkers: array[0.. 3499, GlobalMarkerProc] + gHeapidGenerator: int + +proc nimRegisterGlobalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} = + if globalMarkersLen <= high(globalMarkers): + globalMarkers[globalMarkersLen] = markerProc + inc globalMarkersLen + else: + echo "[GC] cannot register global variable; too many global variables" + quit 1 + +proc nimRegisterThreadLocalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} = + if threadLocalMarkersLen <= high(threadLocalMarkers): + threadLocalMarkers[threadLocalMarkersLen] = markerProc + inc threadLocalMarkersLen + else: + echo "[GC] cannot register thread local variable; too many thread local variables" + quit 1 diff --git a/lib/system/gc_ms.nim b/lib/system/gc_ms.nim index 272047bb7b..101185dfbd 100644 --- a/lib/system/gc_ms.nim +++ b/lib/system/gc_ms.nim @@ -40,8 +40,6 @@ type # A ref type can have a finalizer that is called before the object's # storage is freed. - GlobalMarkerProc = proc () {.nimcall, benign.} - GcStat = object collections: int # number of performed full collections maxThreshold: int # max threshold that has been set @@ -75,9 +73,9 @@ type stat: GcStat when hasThreadSupport: toDispose: SharedList[pointer] + gcThreadId: int additionalRoots: CellSeq # dummy roots for GC_ref/unref -{.deprecated: [TWalkOp: WalkOp, TFinalizer: Finalizer, TGcStat: GcStat, - TGlobalMarkerProc: GlobalMarkerProc, TGcHeap: GcHeap].} + var gch {.rtlThreadVar.}: GcHeap @@ -119,18 +117,6 @@ proc unsureAsgnRef(dest: PPointer, src: pointer) {.inline.} = proc internRefcount(p: pointer): int {.exportc: "getRefcount".} = result = 0 -var - globalMarkersLen: int - globalMarkers: array[0.. 7_000, GlobalMarkerProc] - -proc nimRegisterGlobalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} = - if globalMarkersLen <= high(globalMarkers): - globalMarkers[globalMarkersLen] = markerProc - inc globalMarkersLen - else: - echo "[GC] cannot register global variable; too many global variables" - quit 1 - # this that has to equals zero, otherwise we have to round up UnitsPerPage: when BitsPerPage mod (sizeof(int)*8) != 0: {.error: "(BitsPerPage mod BitsPerUnit) should be zero!".} @@ -233,7 +219,9 @@ proc initGC() = init(gch.allocated) init(gch.marked) when hasThreadSupport: - gch.toDispose = initSharedList[pointer]() + init(gch.toDispose) + gch.gcThreadId = atomicInc(gHeapidGenerator) - 1 + gcAssert(gch.gcThreadId >= 0, "invalid computed thread ID") proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) {.benign.} = var d = cast[ByteAddress](dest) @@ -407,13 +395,7 @@ proc doOperation(p: pointer, op: WalkOp) = var c: PCell = usrToCell(p) gcAssert(c != nil, "doOperation: 1") case op - of waMarkGlobal: - when hasThreadSupport: - # could point to a cell which we don't own and don't want to touch/trace - if isAllocatedPtr(gch.region, c): - mark(gch, c) - else: - mark(gch, c) + of waMarkGlobal: mark(gch, c) of waMarkPrecise: add(gch.tempStack, c) proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} = @@ -450,7 +432,9 @@ when false: quit 1 proc markGlobals(gch: var GcHeap) = - for i in 0 .. globalMarkersLen-1: globalMarkers[i]() + if gch.gcThreadId == 0: + for i in 0 .. globalMarkersLen-1: globalMarkers[i]() + for i in 0 .. threadLocalMarkersLen-1: threadLocalMarkers[i]() let d = gch.additionalRoots.d for i in 0 .. gch.additionalRoots.len-1: mark(gch, d[i]) diff --git a/lib/system/genodealloc.nim b/lib/system/genodealloc.nim new file mode 100644 index 0000000000..3646a842d8 --- /dev/null +++ b/lib/system/genodealloc.nim @@ -0,0 +1,114 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Emery Hemingway +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# Low level dataspace allocator for Genode. + +when not defined(genode): + {.error: "Genode only module".} + +type DataspaceCapability {. + importcpp: "Genode::Dataspace_capability", pure.} = object + +type + Map = object + attachment: pointer + size: int + ds: DataspaceCapability + + SlabMeta = object + next: ptr MapSlab + ds: DataspaceCapability + + MapSlab = object + meta: SlabMeta + maps: array[1,Map] + +const SlabBackendSize = 4096 + +proc ramAvail(): int {. + importcpp: "genodeEnv->pd().avail_ram().value".} + ## Return number of bytes available for allocation. + +proc capsAvail(): int {. + importcpp: "genodeEnv->pd().avail_caps().value".} + ## Return the number of available capabilities. + ## Each dataspace allocation consumes a capability. + +proc allocDataspace(size: int): DataspaceCapability {. + importcpp: "genodeEnv->pd().alloc(@)".} + ## Allocate a dataspace and its capability. + +proc attachDataspace(ds: DataspaceCapability): pointer {. + importcpp: "genodeEnv->rm().attach(@)".} + ## Attach a dataspace into the component address-space. + +proc detachAddress(p: pointer) {. + importcpp: "genodeEnv->rm().detach(@)".} + ## Detach a dataspace from the component address-space. + +proc freeDataspace(ds: DataspaceCapability) {. + importcpp: "genodeEnv->pd().free(@)".} + ## Free a dataspace. + +proc newMapSlab(): ptr MapSlab = + let + ds = allocDataspace SlabBackendSize + p = attachDataspace ds + result = cast[ptr MapSlab](p) + result.meta.ds = ds + +iterator items(s: ptr MapSlab): ptr Map = + let mapCount = (SlabBackendSize - sizeof(SlabMeta)) div sizeof(Map) + for i in 0 .. = size and capsAvail() > 1: + result = osAllocPages size + +proc osDeallocPages(p: pointer; size: int) = + var slab = slabs + while not slab.isNil: + # lookup first free spot in slabs + for m in slab.items: + if m.attachment == p: + if m.size != size: + echo "cannot partially detach dataspace" + quit -1 + detachAddress m.attachment + freeDataspace m.ds + m[] = Map() + return + slab = slab.meta.next diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index 9ac039e192..45e0c74c0e 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -109,7 +109,6 @@ when defined(boehmgc): if result == nil: raiseOutOfMem() proc alloc0(size: Natural): pointer = result = alloc(size) - zeroMem(result, size) proc realloc(p: pointer, newsize: Natural): pointer = result = boehmRealloc(p, newsize) if result == nil: raiseOutOfMem() @@ -119,8 +118,7 @@ when defined(boehmgc): result = boehmAlloc(size) if result == nil: raiseOutOfMem() proc allocShared0(size: Natural): pointer = - result = alloc(size) - zeroMem(result, size) + result = allocShared(size) proc reallocShared(p: pointer, newsize: Natural): pointer = result = boehmRealloc(p, newsize) if result == nil: raiseOutOfMem() diff --git a/lib/system/osalloc.nim b/lib/system/osalloc.nim index 444f113069..1ad4cf6952 100644 --- a/lib/system/osalloc.nim +++ b/lib/system/osalloc.nim @@ -78,17 +78,7 @@ when defined(emscripten): munmap(mmapDescr.realPointer, mmapDescr.realSize) elif defined(genode): - - proc osAllocPages(size: int): pointer {. - importcpp: "genodeEnv->rm().attach(genodeEnv->ram().alloc(@))".} - - proc osTryAllocPages(size: int): pointer = - {.emit: """try {""".} - result = osAllocPages size - {.emit: """} catch (...) { }""".} - - proc osDeallocPages(p: pointer, size: int) {. - importcpp: "genodeEnv->rm().detach(#)".} + include genodealloc # osAllocPages, osTryAllocPages, osDeallocPages elif defined(posix): const diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim index 4348ffbb50..f638b299ce 100644 --- a/lib/system/sysio.nim +++ b/lib/system/sysio.nim @@ -406,7 +406,8 @@ proc setStdIoUnbuffered() = when declared(stdout): proc echoBinSafe(args: openArray[string]) {.compilerProc.} = - when not defined(windows): + # flockfile deadlocks some versions of Android 5.x.x + when not defined(windows) and not defined(android): proc flockfile(f: File) {.importc, noDecl.} proc funlockfile(f: File) {.importc, noDecl.} flockfile(stdout) @@ -415,7 +416,7 @@ when declared(stdout): const linefeed = "\n" # can be 1 or more chars discard c_fwrite(linefeed.cstring, linefeed.len, 1, stdout) discard c_fflush(stdout) - when not defined(windows): + when not defined(windows) and not defined(android): funlockfile(stdout) {.pop.} diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index 56b8ade97a..514223af21 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -24,10 +24,10 @@ proc cmpStrings(a, b: NimString): int {.inline, compilerProc.} = if a == b: return 0 if a == nil: return -1 if b == nil: return 1 - when defined(nimNoArrayToCstringConversion): - return c_strcmp(addr a.data, addr b.data) - else: - return c_strcmp(a.data, b.data) + let minlen = min(a.len, b.len) + result = c_memcmp(addr a.data, addr b.data, minlen.csize) + if result == 0: + result = a.len - b.len proc eqStrings(a, b: NimString): bool {.inline, compilerProc.} = if a == b: return true @@ -386,8 +386,7 @@ proc nimParseBiggestFloat(s: string, number: var BiggestFloat, kdigits, fdigits = 0 exponent: int integer: uint64 - fraction: uint64 - frac_exponent= 0 + frac_exponent = 0 exp_sign = 1 first_digit = -1 has_sign = false @@ -480,7 +479,8 @@ proc nimParseBiggestFloat(s: string, number: var BiggestFloat, # if integer is representable in 53 bits: fast path # max fast path integer is 1<<53 - 1 or 8999999999999999 (16 digits) - if kdigits + fdigits <= 16 and first_digit <= 8: + let digits = kdigits + fdigits + if digits <= 15 or (digits <= 16 and first_digit <= 8): # max float power of ten with set bits above the 53th bit is 10^22 if abs_exponent <= 22: if exp_negative: @@ -504,6 +504,7 @@ proc nimParseBiggestFloat(s: string, number: var BiggestFloat, result = i - start i = start # re-parse without error checking, any error should be handled by the code above. + if s[i] == '.': i.inc while s[i] in {'0'..'9','+','-'}: if ti < maxlen: t[ti] = s[i]; inc(ti) diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 7eb268a9a4..bd6e58a10f 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -111,6 +111,7 @@ const WAIT_TIMEOUT* = 0x00000102'i32 WAIT_FAILED* = 0xFFFFFFFF'i32 INFINITE* = -1'i32 + STILL_ACTIVE* = 0x00000103'i32 STD_INPUT_HANDLE* = -10'i32 STD_OUTPUT_HANDLE* = -11'i32 @@ -685,6 +686,7 @@ const ERROR_FILE_NOT_FOUND* = 2 ERROR_PATH_NOT_FOUND* = 3 ERROR_ACCESS_DENIED* = 5 + ERROR_NO_MORE_FILES* = 18 ERROR_HANDLE_EOF* = 38 ERROR_BAD_ARGUMENTS* = 165 diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim index 431ea59122..1d0dc5897f 100644 --- a/lib/wrappers/openssl.nim +++ b/lib/wrappers/openssl.nim @@ -8,6 +8,17 @@ # ## OpenSSL support +## +## When OpenSSL is dynamically linked, the wrapper provides partial forward and backward +## compatibility for OpenSSL versions above and below 1.1.0 +## +## OpenSSL can be also statically linked using dynlibOverride:ssl for OpenSSL >= 1.1.0 +## +## Build and test examples: +## +## .. code-block:: +## ./bin/nim c -d:ssl -p:. -r tests/untestable/tssl.nim +## ./bin/nim c -d:ssl -p:. --dynlibOverride:ssl --passL:-lcrypto --passL:-lssl -r tests/untestable/tssl.nim {.deadCodeElim: on.} @@ -25,8 +36,8 @@ when useWinVersion: from winlean import SocketHandle else: - const - versions = "(|.38|.39|.41|.43|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8)" + const versions = "(.1.1|.38|.39|.41|.43|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|)" + when defined(macosx): const DLLSSLName = "libssl" & versions & ".dylib" @@ -64,6 +75,8 @@ type des_key_schedule* = array[1..16, des_ks_struct] + pem_password_cb* = proc(buf: cstring, size, rwflag: cint, userdata: pointer): cint {.cdecl.} + {.deprecated: [PSSL: SslPtr, PSSL_CTX: SslCtx, PBIO: BIO].} const @@ -138,6 +151,7 @@ const SSL_OP_NO_SSLv2* = 0x01000000 SSL_OP_NO_SSLv3* = 0x02000000 SSL_OP_NO_TLSv1* = 0x04000000 + SSL_OP_NO_TLSv1_1* = 0x08000000 SSL_OP_ALL* = 0x000FFFFF SSL_VERIFY_NONE* = 0x00000000 SSL_VERIFY_PEER* = 0x00000001 @@ -189,16 +203,39 @@ const proc TLSv1_method*(): PSSL_METHOD{.cdecl, dynlib: DLLSSLName, importc.} -when compileOption("dynlibOverride", "ssl"): - proc SSL_library_init*(): cint {.cdecl, dynlib: DLLSSLName, importc, discardable.} - proc SSL_load_error_strings*() {.cdecl, dynlib: DLLSSLName, importc.} - proc SSLv23_client_method*(): PSSL_METHOD {.cdecl, dynlib: DLLSSLName, importc.} +# TLS_method(), TLS_server_method(), TLS_client_method() are introduced in 1.1.0 +# and support SSLv3, TLSv1, TLSv1.1 and TLSv1.2 +# SSLv23_method(), SSLv23_server_method(), SSLv23_client_method() are removed in 1.1.0 - proc SSLv23_method*(): PSSL_METHOD {.cdecl, dynlib: DLLSSLName, importc.} +when compileOption("dynlibOverride", "ssl"): + # Static linking + proc OPENSSL_init_ssl*(opts: uint64, settings: uint8): cint {.cdecl, dynlib: DLLSSLName, importc, discardable.} + proc SSL_library_init*(): cint {.discardable.} = + ## Initialize SSL using OPENSSL_init_ssl for OpenSSL >= 1.1.0 + return OPENSSL_init_ssl(0.uint64, 0.uint8) + + proc TLS_method*(): PSSL_METHOD {.cdecl, dynlib: DLLSSLName, importc.} + proc SSLv23_method*(): PSSL_METHOD = + TLS_method() + + proc SSLv23_client_method*(): PSSL_METHOD {.cdecl, dynlib: DLLSSLName, importc.} proc SSLv2_method*(): PSSL_METHOD {.cdecl, dynlib: DLLSSLName, importc.} proc SSLv3_method*(): PSSL_METHOD {.cdecl, dynlib: DLLSSLName, importc.} template OpenSSL_add_all_algorithms*() = discard + + proc OpenSSL_version_num(): culong {.cdecl, dynlib: DLLSSLName, importc.} + + proc getOpenSSLVersion*(): culong = + ## Return OpenSSL version as unsigned long + OpenSSL_version_num() + + proc SSL_load_error_strings*() = + ## Removed from OpenSSL 1.1.0 + # This proc prevents breaking existing code calling SslLoadErrorStrings + # Static linking against OpenSSL < 1.1.0 is not supported + discard + else: # Here we're trying to stay compatible with openssl 1.0.* and 1.1.*. Some # symbols are loaded dynamically and we don't use them if not found. @@ -221,38 +258,58 @@ else: if not dl.isNil: result = symAddr(dl, name) + proc loadPSSLMethod(method1, method2: string): PSSL_METHOD = + ## Load from OpenSSL if available, otherwise + let m1 = cast[proc(): PSSL_METHOD {.cdecl, gcsafe.}](sslSym(method1)) + if not m1.isNil: + return m1() + cast[proc(): PSSL_METHOD {.cdecl, gcsafe.}](sslSym(method2))() + proc SSL_library_init*(): cint {.discardable.} = - let theProc = cast[proc(): cint {.cdecl.}](sslSym("SSL_library_init")) - if not theProc.isNil: result = theProc() + ## Initialize SSL using OPENSSL_init_ssl for OpenSSL >= 1.1.0 otherwise + ## SSL_library_init + let theProc = cast[proc(opts: uint64, settings: uint8): cint {.cdecl.}](sslSym("OPENSSL_init_ssl")) + if not theProc.isNil: + return theProc(0, 0) + let olderProc = cast[proc(): cint {.cdecl.}](sslSym("SSL_library_init")) + if not olderProc.isNil: result = olderProc() proc SSL_load_error_strings*() = let theProc = cast[proc() {.cdecl.}](sslSym("SSL_load_error_strings")) if not theProc.isNil: theProc() proc SSLv23_client_method*(): PSSL_METHOD = - let theProc = cast[proc(): PSSL_METHOD {.cdecl, gcsafe.}](sslSym("SSLv23_client_method")) - if not theProc.isNil: result = theProc() - else: result = TLSv1_method() + loadPSSLMethod("SSLv23_client_method", "TLS_client_method") proc SSLv23_method*(): PSSL_METHOD = - let theProc = cast[proc(): PSSL_METHOD {.cdecl, gcsafe.}](sslSym("SSLv23_method")) - if not theProc.isNil: result = theProc() - else: result = TLSv1_method() + loadPSSLMethod("SSLv23_method", "TLS_method") proc SSLv2_method*(): PSSL_METHOD = - let theProc = cast[proc(): PSSL_METHOD {.cdecl, gcsafe.}](sslSym("SSLv2_method")) - if not theProc.isNil: result = theProc() - else: result = TLSv1_method() + loadPSSLMethod("SSLv2_method", "TLS_method") proc SSLv3_method*(): PSSL_METHOD = - let theProc = cast[proc(): PSSL_METHOD {.cdecl, gcsafe.}](sslSym("SSLv3_method")) - if not theProc.isNil: result = theProc() - else: result = TLSv1_method() + loadPSSLMethod("SSLv3_method", "TLS_method") + + proc TLS_method*(): PSSL_METHOD = + loadPSSLMethod("TLS_method", "SSLv23_method") + + proc TLS_client_method*(): PSSL_METHOD = + loadPSSLMethod("TLS_client_method", "SSLv23_client_method") + + proc TLS_server_method*(): PSSL_METHOD = + loadPSSLMethod("TLS_server_method", "SSLv23_server_method") proc OpenSSL_add_all_algorithms*() = let theProc = cast[proc() {.cdecl.}](sslSym("OPENSSL_add_all_algorithms_conf")) if not theProc.isNil: theProc() + proc getOpenSSLVersion*(): culong = + ## Return OpenSSL version as unsigned long or 0 if not available + let theProc = cast[proc(): culong {.cdecl.}](sslSym("OpenSSL_version_num")) + result = + if theProc.isNil: 0.culong + else: theProc() + proc ERR_load_BIO_strings*(){.cdecl, dynlib: DLLUtilName, importc.} proc SSL_new*(context: SslCtx): SslPtr{.cdecl, dynlib: DLLSSLName, importc.} @@ -432,6 +489,12 @@ proc ErrClearError*(){.cdecl, dynlib: DLLUtilName, importc: "ERR_clear_error".} proc ErrFreeStrings*(){.cdecl, dynlib: DLLUtilName, importc: "ERR_free_strings".} proc ErrRemoveState*(pid: cInt){.cdecl, dynlib: DLLUtilName, importc: "ERR_remove_state".} +proc PEM_read_bio_RSA_PUBKEY*(bp: BIO, x: ptr PRSA, pw: pem_password_cb, u: pointer): PRSA {.cdecl, + dynlib: DLLSSLName, importc.} + +proc RSA_verify*(kind: cint, origMsg: pointer, origMsgLen: cuint, signature: pointer, + signatureLen: cuint, rsa: PRSA): cint {.cdecl, dynlib: DLLSSLName, importc.} + when true: discard else: diff --git a/tests/array/troof1.nim b/tests/array/troof1.nim index 594ad98a57..b486c34482 100644 --- a/tests/array/troof1.nim +++ b/tests/array/troof1.nim @@ -4,7 +4,7 @@ discard """ 3 @[(Field0: 1, Field1: 2), (Field0: 3, Field1: 5)] 2 -@[a, new one, c] +@["a", "new one", "c"] @[1, 2, 3]''' """ diff --git a/tests/async/t6100.nim b/tests/async/t6100.nim new file mode 100644 index 0000000000..b4dc0f1469 --- /dev/null +++ b/tests/async/t6100.nim @@ -0,0 +1,15 @@ +discard """ + file: "t6100.nim" + exitcode: 0 + output: "10000000" +""" +import asyncdispatch + +let done = newFuture[int]() +done.complete(1) + +proc asyncSum: Future[int] {.async.} = + for _ in 1..10_000_000: + result += await done + +echo waitFor asyncSum() \ No newline at end of file diff --git a/tests/async/tasync_in_seq_constr.nim b/tests/async/tasync_in_seq_constr.nim index 7d216e352f..46ad744512 100644 --- a/tests/async/tasync_in_seq_constr.nim +++ b/tests/async/tasync_in_seq_constr.nim @@ -1,8 +1,9 @@ discard """ - output: '''@[1, 2, 3, 4]''' + errormsg: "invalid control flow: 'yield' within a constructor" + line: 16 """ -# bug #5314 +# bug #5314, bug #6626 import asyncdispatch diff --git a/tests/async/tasync_traceback.nim b/tests/async/tasync_traceback.nim new file mode 100644 index 0000000000..e4c8a67b34 --- /dev/null +++ b/tests/async/tasync_traceback.nim @@ -0,0 +1,126 @@ +discard """ + exitcode: 0 + disabled: "windows" + output: "Matched" +""" +import asyncdispatch + +# Tests to ensure our exception trace backs are friendly. + +# --- Simple test. --- +# +# What does this look like when it's synchronous? +# +# tasync_traceback.nim(23) tasync_traceback +# tasync_traceback.nim(21) a +# tasync_traceback.nim(18) b +# Error: unhandled exception: b failure [OSError] +# +# Good (not quite ideal, but gotta work within constraints) traceback, +# when exception is unhandled: +# +# +# +# +# <(the code responsible is in excpt:raiseExceptionAux)> +# Error: unhandled exception: b failure +# =============== +# Async traceback +# =============== +# +# tasync_traceback.nim(23) tasync_traceback +# +# tasync_traceback.nim(21) a +# tasync_traceback.nim(18) b + +var result = "" + +proc b(): Future[int] {.async.} = + if true: + raise newException(OSError, "b failure") + +proc a(): Future[int] {.async.} = + return await b() + +let aFut = a() +try: + discard waitFor aFut +except Exception as exc: + result.add(exc.msg & "\n") +result.add("\n") + +# From #6803 +proc bar(): Future[string] {.async.} = + await sleepAsync(100) + if true: + raise newException(OSError, "bar failure") + +proc foo(): Future[string] {.async.} = return await bar() + +try: + result.add(waitFor(foo()) & "\n") +except Exception as exc: + result.add(exc.msg & "\n") +result.add("\n") + +# Use re to parse the result +import re +const expected = """ +b failure +Async traceback: + tasync_traceback\.nim\(\d+?\)\s+?tasync_traceback + asyncmacro\.nim\(\d+?\)\s+?a + asyncmacro\.nim\(\d+?\)\s+?a_continue + ## Resumes an async procedure + tasync_traceback\.nim\(\d+?\)\s+?aIter + asyncmacro\.nim\(\d+?\)\s+?b + asyncmacro\.nim\(\d+?\)\s+?b_continue + ## Resumes an async procedure + tasync_traceback\.nim\(\d+?\)\s+?bIter + #\[ + tasync_traceback\.nim\(\d+?\)\s+?tasync_traceback + asyncmacro\.nim\(\d+?\)\s+?a + asyncmacro\.nim\(\d+?\)\s+?a_continue + ## Resumes an async procedure + asyncmacro\.nim\(\d+?\)\s+?aIter + asyncfutures\.nim\(\d+?\)\s+?read + \]# +Exception message: b failure +Exception type: + +bar failure +Async traceback: + tasync_traceback\.nim\(\d+?\)\s+?tasync_traceback + asyncdispatch\.nim\(\d+?\)\s+?waitFor + asyncdispatch\.nim\(\d+?\)\s+?poll + ## Processes asynchronous completion events + asyncdispatch\.nim\(\d+?\)\s+?runOnce + asyncdispatch\.nim\(\d+?\)\s+?processPendingCallbacks + ## Executes pending callbacks + asyncmacro\.nim\(\d+?\)\s+?bar_continue + ## Resumes an async procedure + tasync_traceback\.nim\(\d+?\)\s+?barIter + #\[ + tasync_traceback\.nim\(\d+?\)\s+?tasync_traceback + asyncdispatch\.nim\(\d+?\)\s+?waitFor + asyncdispatch\.nim\(\d+?\)\s+?poll + ## Processes asynchronous completion events + asyncdispatch\.nim\(\d+?\)\s+?runOnce + asyncdispatch\.nim\(\d+?\)\s+?processPendingCallbacks + ## Executes pending callbacks + asyncmacro\.nim\(\d+?\)\s+?foo_continue + ## Resumes an async procedure + asyncmacro\.nim\(\d+?\)\s+?fooIter + asyncfutures\.nim\(\d+?\)\s+?read + \]# +Exception message: bar failure +Exception type: +""" + +if result.match(re(expected)): + echo("Matched") +else: + echo("Not matched!") + echo() + echo(result) + quit(QuitFailure) diff --git a/tests/async/tfuturestream.nim b/tests/async/tfuturestream.nim index 9a8e986a09..d76752b7ea 100644 --- a/tests/async/tfuturestream.nim +++ b/tests/async/tfuturestream.nim @@ -18,8 +18,8 @@ var fs = newFutureStream[int]() proc alpha() {.async.} = for i in 0 .. 5: - await sleepAsync(1000) await fs.write(i) + await sleepAsync(1000) echo("Done") fs.complete() diff --git a/tests/async/tioselectors.nim b/tests/async/tioselectors.nim index 48043b4b56..d2e4cfec11 100644 --- a/tests/async/tioselectors.nim +++ b/tests/async/tioselectors.nim @@ -579,9 +579,9 @@ else: var event = newSelectEvent() selector.registerEvent(event, 1) discard selector.select(0) - event.setEvent() + event.trigger() var rc1 = selector.select(0) - event.setEvent() + event.trigger() var rc2 = selector.select(0) var rc3 = selector.select(0) assert(len(rc1) == 1 and len(rc2) == 1 and len(rc3) == 0) @@ -611,7 +611,7 @@ else: var event = newSelectEvent() for i in 0..high(thr): createThread(thr[i], event_wait_thread, event) - event.setEvent() + event.trigger() joinThreads(thr) assert(counter == 1) result = true diff --git a/tests/casestmt/tcasestm.nim b/tests/casestmt/tcasestm.nim index 7ac20bf2f6..b005d8120e 100644 --- a/tests/casestmt/tcasestm.nim +++ b/tests/casestmt/tcasestm.nim @@ -36,5 +36,64 @@ var z = case i echo z #OUT ayyy +let str1 = "Y" +let str2 = "NN" +let a = case str1: + of "Y": true + of "N": false + else: + echo "no good" + quit("quiting") + +let b = case str2: + of nil, "": raise newException(ValueError, "Invalid boolean") + elif str2[0] == 'Y': true + elif str2[0] == 'N': false + else: "error".quit(2) + +doAssert(a == true) +doAssert(b == false) + +var bb: bool +doassert(not compiles( + bb = case str2: + of nil, "": raise newException(ValueError, "Invalid boolean") + elif str.startsWith("Y"): true + elif str.startsWith("N"): false +)) + +doassert(not compiles( + bb = case str2: + of "Y": true + of "N": false +)) + +doassert(not compiles( + bb = case str2: + of "Y": true + of "N": raise newException(ValueError, "N not allowed") +)) + +doassert(not compiles( + bb = case str2: + of "Y": raise newException(ValueError, "Invalid Y") + else: raise newException(ValueError, "Invalid N") +)) +doassert(not compiles( + bb = case str2: + of "Y": + raise newException(ValueError, "Invalid Y") + true + else: raise newException(ValueError, "Invalid") +)) + + +doassert(not compiles( + bb = case str2: + of "Y": + "invalid Y".quit(3) + true + else: raise newException(ValueError, "Invalid") +)) \ No newline at end of file diff --git a/tests/ccgbugs/t6279.nim b/tests/ccgbugs/t6279.nim index 37345d807b..5a3d6768cc 100644 --- a/tests/ccgbugs/t6279.nim +++ b/tests/ccgbugs/t6279.nim @@ -1,6 +1,6 @@ discard """ cmd: "nim c -r -d:fulldebug -d:smokeCycles --gc:refc $file" -output: '''@[a]''' +output: '''@["a"]''' """ # bug #6279 diff --git a/tests/ccgbugs/tbug1081.nim b/tests/ccgbugs/tbug1081.nim index c9a9e6aa47..baef99d844 100644 --- a/tests/ccgbugs/tbug1081.nim +++ b/tests/ccgbugs/tbug1081.nim @@ -3,7 +3,7 @@ discard """ 0 0 0 -x = [a, b, c, 0, 1, 2, 3, 4, 5, 6] and y = [a, b, c, 0, 1, 2, 3, 4, 5, 6]''' +x = ['a', 'b', 'c', '0', '1', '2', '3', '4', '5', '6'] and y = ['a', 'b', 'c', '0', '1', '2', '3', '4', '5', '6']''' """ proc `1/1`() = echo(1 div 1) diff --git a/tests/ccgbugs/tconstobj.nim b/tests/ccgbugs/tconstobj.nim index 98f441e83d..51cf661ee8 100644 --- a/tests/ccgbugs/tconstobj.nim +++ b/tests/ccgbugs/tconstobj.nim @@ -1,5 +1,5 @@ discard """ - output: '''(FirstName: James, LastName: Franco)''' + output: '''(FirstName: "James", LastName: "Franco")''' """ # bug #1547 diff --git a/tests/ccgbugs/tobjconstr_bad_aliasing.nim b/tests/ccgbugs/tobjconstr_bad_aliasing.nim new file mode 100644 index 0000000000..ea51ecacb6 --- /dev/null +++ b/tests/ccgbugs/tobjconstr_bad_aliasing.nim @@ -0,0 +1,26 @@ +discard """ + output: '''(10, (20, ))''' +""" + +import strutils, sequtils + +# bug #668 + +type + TThing = ref object + data: int + children: seq[TThing] + +proc `$`(t: TThing): string = + result = "($1, $2)" % @[$t.data, join(map(t.children, proc(th: TThing): string = $th), ", ")] + +proc somethingelse(): seq[TThing] = + result = @[TThing(data: 20, children: @[])] + +proc dosomething(): seq[TThing] = + result = somethingelse() + + result = @[TThing(data: 10, children: result)] + +when isMainModule: + echo($dosomething()[0]) diff --git a/tests/ccgbugs/tobjconstr_regression.nim b/tests/ccgbugs/tobjconstr_regression.nim index 87d0378940..d29abad974 100644 --- a/tests/ccgbugs/tobjconstr_regression.nim +++ b/tests/ccgbugs/tobjconstr_regression.nim @@ -1,5 +1,5 @@ discard """ - output: "@[(username: user, role: admin, description: desc, email_addr: email), (username: user, role: admin, description: desc, email_addr: email)]" + output: '''@[(username: "user", role: "admin", description: "desc", email_addr: "email"), (username: "user", role: "admin", description: "desc", email_addr: "email")]''' """ type diff --git a/tests/collections/tcollections_to_string.nim b/tests/collections/tcollections_to_string.nim new file mode 100644 index 0000000000..6cc8a84ff0 --- /dev/null +++ b/tests/collections/tcollections_to_string.nim @@ -0,0 +1,106 @@ +discard """ + exitcode: 0 + output: "" +""" +import sets +import tables +import deques +import lists +import critbits + +# Tests for tuples +doAssert $(1, 2, 3) == "(Field0: 1, Field1: 2, Field2: 3)" +doAssert $("1", "2", "3") == """(Field0: "1", Field1: "2", Field2: "3")""" +doAssert $('1', '2', '3') == """(Field0: '1', Field1: '2', Field2: '3')""" + +# Tests for seqs +doAssert $(@[1, 2, 3]) == "@[1, 2, 3]" +doAssert $(@["1", "2", "3"]) == """@["1", "2", "3"]""" +doAssert $(@['1', '2', '3']) == """@['1', '2', '3']""" + +# Tests for sets +doAssert $(toSet([1])) == "{1}" +doAssert $(toSet(["1"])) == """{"1"}""" +doAssert $(toSet(['1'])) == """{'1'}""" +doAssert $(toOrderedSet([1, 2, 3])) == "{1, 2, 3}" +doAssert $(toOrderedSet(["1", "2", "3"])) == """{"1", "2", "3"}""" +doAssert $(toOrderedSet(['1', '2', '3'])) == """{'1', '2', '3'}""" + +# Tests for tables +doAssert $({1: "1", 2: "2"}.toTable) == """{1: "1", 2: "2"}""" +doAssert $({"1": 1, "2": 2}.toTable) == """{"1": 1, "2": 2}""" + +# Tests for deques +block: + var d = initDeque[int]() + d.addLast(1) + doAssert $d == "[1]" +block: + var d = initDeque[string]() + d.addLast("1") + doAssert $d == """["1"]""" +block: + var d = initDeque[char]() + d.addLast('1') + doAssert $d == "['1']" + +# Tests for lists +block: + var l = initDoublyLinkedList[int]() + l.append(1) + l.append(2) + l.append(3) + doAssert $l == "[1, 2, 3]" +block: + var l = initDoublyLinkedList[string]() + l.append("1") + l.append("2") + l.append("3") + doAssert $l == """["1", "2", "3"]""" +block: + var l = initDoublyLinkedList[char]() + l.append('1') + l.append('2') + l.append('3') + doAssert $l == """['1', '2', '3']""" + +# Tests for critbits +block: + var t: CritBitTree[int] + t["a"] = 1 + doAssert $t == "{a: 1}" +block: + var t: CritBitTree[string] + t["a"] = "1" + doAssert $t == """{a: "1"}""" +block: + var t: CritBitTree[char] + t["a"] = '1' + doAssert $t == "{a: '1'}" + + +# Test escaping behavior +block: + var s = "" + s.addQuoted('\0') + s.addQuoted('\31') + s.addQuoted('\127') + s.addQuoted('\255') + doAssert s == "'\\x00''\\x1F''\\x7F''\\xFF'" +block: + var s = "" + s.addQuoted('\\') + s.addQuoted('\'') + s.addQuoted('\"') + doAssert s == """'\\''\'''\"'""" + +# Test customized element representation +type CustomString = object + +proc addQuoted(s: var string, x: CustomString) = + s.add("") + +block: + let s = @[CustomString()] + doAssert $s == "@[]" + diff --git a/tests/collections/ttables.nim b/tests/collections/ttables.nim index 01dab44fca..7fe4c79b13 100644 --- a/tests/collections/ttables.nim +++ b/tests/collections/ttables.nim @@ -48,7 +48,7 @@ block tableTest1: for y in 0..1: assert t[(x,y)] == $x & $y assert($t == - "{(x: 0, y: 1): 01, (x: 0, y: 0): 00, (x: 1, y: 0): 10, (x: 1, y: 1): 11}") + "{(x: 0, y: 1): \"01\", (x: 0, y: 0): \"00\", (x: 1, y: 0): \"10\", (x: 1, y: 1): \"11\"}") block tableTest2: var t = initTable[string, float]() @@ -213,7 +213,8 @@ block clearCountTableTest: assert t.len() == 0 block withKeyTest: - var t = initSharedTable[int, int]() + var t: SharedTable[int, int] + t.init() t.withKey(1) do (k: int, v: var int, pairExists: var bool): assert(v == 0) pairExists = true diff --git a/tests/collections/ttablesref.nim b/tests/collections/ttablesref.nim index 12af1ccbb8..a4030e0dce 100644 --- a/tests/collections/ttablesref.nim +++ b/tests/collections/ttablesref.nim @@ -47,7 +47,7 @@ block tableTest1: for y in 0..1: assert t[(x,y)] == $x & $y assert($t == - "{(x: 0, y: 1): 01, (x: 0, y: 0): 00, (x: 1, y: 0): 10, (x: 1, y: 1): 11}") + "{(x: 0, y: 1): \"01\", (x: 0, y: 0): \"00\", (x: 1, y: 0): \"10\", (x: 1, y: 1): \"11\"}") block tableTest2: var t = newTable[string, float]() @@ -139,7 +139,7 @@ proc orderedTableSortTest() = block anonZipTest: let keys = @['a','b','c'] let values = @[1, 2, 3] - doAssert "{a: 1, b: 2, c: 3}" == $ toTable zip(keys, values) + doAssert "{'a': 1, 'b': 2, 'c': 3}" == $ toTable zip(keys, values) block clearTableTest: var t = newTable[string, float]() diff --git a/tests/concepts/t3330.nim b/tests/concepts/t3330.nim index fcd5054ef4..bf8bffc7d4 100644 --- a/tests/concepts/t3330.nim +++ b/tests/concepts/t3330.nim @@ -8,8 +8,8 @@ t3330.nim(25, 8) Hint: Non-matching candidates for add(k, string, T) proc add(x: var string; y: string) proc add(x: var string; y: char) proc add(result: var string; x: int64) -proc add(x: var string; y: cstring) proc add(result: var string; x: float) +proc add(x: var string; y: cstring) proc add[T](x: var seq[T]; y: openArray[T]) proc add[T](x: var seq[T]; y: T) diff --git a/tests/concepts/t6462.nim b/tests/concepts/t6462.nim new file mode 100644 index 0000000000..2fa2268f83 --- /dev/null +++ b/tests/concepts/t6462.nim @@ -0,0 +1,23 @@ +discard """ + output: "true" +""" + +import future + +type + FilterMixin*[T] = ref object + test*: (T) -> bool + trans*: (T) -> T + + SeqGen*[T] = ref object + fil*: FilterMixin[T] + + WithFilter[T] = concept a + a.fil is FilterMixin[T] + +proc test*[T](a: WithFilter[T]): (T) -> bool = + a.fil.test + +var s = SeqGen[int](fil: FilterMixin[int](test: nil, trans: nil)) +echo s.test() == nil + diff --git a/tests/concepts/tcomparable.nim b/tests/concepts/tcomparable.nim new file mode 100644 index 0000000000..06612a47e7 --- /dev/null +++ b/tests/concepts/tcomparable.nim @@ -0,0 +1,13 @@ +type + Comparable = concept a + (a < a) is bool + +proc myMax(a, b: Comparable): Comparable = + if a < b: + return b + else: + return a + +doAssert myMax(5, 10) == 10 +doAssert myMax(31.3, 1.23124) == 31.3 + diff --git a/tests/concepts/titerable.nim b/tests/concepts/titerable.nim new file mode 100644 index 0000000000..b18658b2a1 --- /dev/null +++ b/tests/concepts/titerable.nim @@ -0,0 +1,20 @@ +discard """ + nimout: "int\nint" + output: 15 +""" + +import typetraits + +type + Iterable[T] = concept x + for value in x: + type(value) is T + +proc sum*[T](iter: Iterable[T]): T = + static: echo T.name + for element in iter: + static: echo element.type.name + result += element + +echo sum([1, 2, 3, 4, 5]) + diff --git a/tests/cpp/tget_subsystem.nim b/tests/cpp/tget_subsystem.nim index e9a3fabdd2..6fb095a3de 100644 --- a/tests/cpp/tget_subsystem.nim +++ b/tests/cpp/tget_subsystem.nim @@ -22,10 +22,18 @@ proc getSubsystem*[T](): ptr T {. let input: ptr Input = getSubsystem[Input]() -# bug #4910 +# bugs #4910, #6892 +proc modify(x: var int) = + x = 123 proc foo() = - var ts: array[10, int] + var ts: array[2, int] for t in mitems(ts): - t = 123 + discard + + for t in mitems(ts): + modify(t) + + for i, t in mpairs(ts): + modify(t) diff --git a/tests/exprs/tstmtexprs.nim b/tests/exprs/tstmtexprs.nim index 01f429b07f..2a0ec2821f 100644 --- a/tests/exprs/tstmtexprs.nim +++ b/tests/exprs/tstmtexprs.nim @@ -1,6 +1,6 @@ discard """ output: '''24 -(bar: bar) +(bar: "bar") 1244 6 abcdefghijklmnopqrstuvwxyz @@ -140,4 +140,14 @@ echo( else: quo do (a: int) -> bool: a mod 3 != 0 -) \ No newline at end of file +) + +# bug #6980 + +proc fooBool: bool {.discardable.} = + true + +if true: + fooBool() +else: + raise newException(ValueError, "argh") diff --git a/tests/fields/timplicitfieldswithpartial.nim b/tests/fields/timplicitfieldswithpartial.nim index a315cc5d0b..9378332577 100644 --- a/tests/fields/timplicitfieldswithpartial.nim +++ b/tests/fields/timplicitfieldswithpartial.nim @@ -1,5 +1,5 @@ discard """ - output: '''(foo: 38, other: string here) + output: '''(foo: 38, other: "string here") 43 100 90''' diff --git a/tests/float/tfloat4.nim b/tests/float/tfloat4.nim index 559c8aacad..68df56be8c 100644 --- a/tests/float/tfloat4.nim +++ b/tests/float/tfloat4.nim @@ -48,5 +48,11 @@ doAssert "2.71828182845904523536028747".parseFloat == 2.71828182845904523536028747 doAssert 0.00097656250000000021684043449710088680149056017398834228515625 == "0.00097656250000000021684043449710088680149056017398834228515625".parseFloat +doAssert 0.00998333 == ".00998333".parseFloat +doAssert 0.00128333 == ".00128333".parseFloat +doAssert 999999999999999.0 == "999999999999999.0".parseFloat +doAssert 9999999999999999.0 == "9999999999999999.0".parseFloat +doAssert 0.999999999999999 == ".999999999999999".parseFloat +doAssert 0.9999999999999999 == ".9999999999999999".parseFloat echo("passed all tests.") diff --git a/tests/fragmentation/tfragment_gc.nim b/tests/fragmentation/tfragment_gc.nim index 6e0ec37cea..3ebed23932 100644 --- a/tests/fragmentation/tfragment_gc.nim +++ b/tests/fragmentation/tfragment_gc.nim @@ -20,8 +20,11 @@ let total = getTotalMem() # Concrete values on Win64: 58.152MiB / 188.285MiB -echo "occupied ok: ", occ < 60 * 1024 * 1024 -let totalOk = total < 210 * 1024 * 1024 +let occupiedOk = occ < 80 * 1024 * 1024 +if not occupiedOk: + echo "occupied ", formatSize(occ) +echo "occupied ok: ", occupiedOk +let totalOk = total < 230 * 1024 * 1024 if not totalOk: echo "total peak memory ", formatSize(total) echo "total ok: ", totalOk diff --git a/tests/gc/gctest.nim b/tests/gc/gctest.nim index f5c81f033a..7f5260200c 100644 --- a/tests/gc/gctest.nim +++ b/tests/gc/gctest.nim @@ -179,24 +179,38 @@ proc main() = write(stdout, "done!\n") var - father: TBNode - s: string -s = "" -s = "" -writeLine(stdout, repr(caseTree())) -father.t.data = @["ha", "lets", "stress", "it"] -father.t.data = @["ha", "lets", "stress", "it"] -var t = buildTree() -write(stdout, repr(t[])) -buildBTree(father) -write(stdout, repr(father)) + father {.threadvar.}: TBNode + s {.threadvar.}: string -write(stdout, "starting main...\n") -main() + fatherAsGlobal: TBNode -GC_fullCollect() -# the M&S GC fails with this call and it's unclear why. Definitely something -# we need to fix! -GC_fullCollect() -writeLine(stdout, GC_getStatistics()) -write(stdout, "finished\n") +proc start = + s = "" + s = "" + writeLine(stdout, repr(caseTree())) + father.t.data = @["ha", "lets", "stress", "it"] + father.t.data = @["ha", "lets", "stress", "it"] + var t = buildTree() + write(stdout, repr(t[])) + buildBTree(father) + write(stdout, repr(father)) + + write(stdout, "starting main...\n") + main() + + GC_fullCollect() + # the M&S GC fails with this call and it's unclear why. Definitely something + # we need to fix! + #GC_fullCollect() + writeLine(stdout, GC_getStatistics()) + write(stdout, "finished\n") + +fatherAsGlobal.t.data = @["ha", "lets", "stress", "it"] +var tg = buildTree() +buildBTree(fatherAsGlobal) + +var thr: array[8, Thread[void]] +for i in low(thr)..high(thr): + createThread(thr[i], start) +joinThreads(thr) +start() diff --git a/tests/gc/gctest.nim.cfg b/tests/gc/gctest.nim.cfg new file mode 100644 index 0000000000..aed303eef8 --- /dev/null +++ b/tests/gc/gctest.nim.cfg @@ -0,0 +1 @@ +--threads:on diff --git a/tests/generics/treentranttypes.nim b/tests/generics/treentranttypes.nim index 9b4774e9bc..2ef049ce2f 100644 --- a/tests/generics/treentranttypes.nim +++ b/tests/generics/treentranttypes.nim @@ -1,6 +1,6 @@ discard """ output: ''' -(Field0: 10, Field1: (Field0: test, Field1: 1.2)) +(Field0: 10, Field1: (Field0: "test", Field1: 1.2)) 3x3 Matrix [[0.0, 2.0, 3.0], [2.0, 0.0, 5.0], [2.0, 0.0, 5.0]] 2x3 Matrix [[0.0, 2.0, 3.0], [2.0, 0.0, 5.0]] @@ -43,7 +43,7 @@ type Matrix*[M: static[int]; N: static[int]; T] = Vector[M, Vector[N, T]] - + proc arrayTest = # every kind of square matrix works just fine let mat_good: Matrix[3, 3, float] = [[0.0, 2.0, 3.0], diff --git a/tests/js/tasync.nim b/tests/js/tasync.nim new file mode 100644 index 0000000000..34ef97b8bf --- /dev/null +++ b/tests/js/tasync.nim @@ -0,0 +1,32 @@ +discard """ + disabled: true + output: ''' +x +e +''' +""" + +import asyncjs + +# demonstrate forward definition +# for js +proc y(e: int): Future[string] {.async.} + +proc e: int {.discardable.} = + echo "e" + return 2 + +proc x(e: int): Future[void] {.async.} = + var s = await y(e) + echo s + e() + +proc y(e: int): Future[string] {.async.} = + if e > 0: + return await y(0) + else: + return "x" + + +discard x(2) + diff --git a/tests/js/trefbyvar.nim b/tests/js/trefbyvar.nim index d440fcc644..5b168044ee 100644 --- a/tests/js/trefbyvar.nim +++ b/tests/js/trefbyvar.nim @@ -66,4 +66,4 @@ proc initTypeA1(a: int; b: string; c: pointer = nil): TypeA1 = result.c_impl = c let x = initTypeA1(1, "a") -doAssert($x == "(a_impl: 1, b_impl: a, c_impl: ...)") +doAssert($x == "(a_impl: 1, b_impl: \"a\", c_impl: ...)") diff --git a/tests/js/ttimes.nim b/tests/js/ttimes.nim index 2868c6d0f0..63a4bb04ff 100644 --- a/tests/js/ttimes.nim +++ b/tests/js/ttimes.nim @@ -1,4 +1,3 @@ -# test times module with js discard """ action: run """ @@ -9,21 +8,37 @@ import times # Tue 19 Jan 03:14:07 GMT 2038 block yeardayTest: - # check if yearday attribute is properly set on TimeInfo creation - doAssert fromSeconds(2147483647).getGMTime().yearday == 18 + doAssert fromUnix(2147483647).utc.yearday == 18 -block localTimezoneTest: - # check if timezone is properly set during Time to TimeInfo conversion - doAssert fromSeconds(2147483647).getLocalTime().timezone == getTimezone() +block localTime: + var local = now() + let utc = local.utc + doAssert local.toTime == utc.toTime -block timestampPersistenceTest: - # check if timestamp persists during TimeInfo to Time conversion - const - timeString = "2017-03-21T12:34:56+03:00" - timeStringGmt = "2017-03-21T09:34:56+00:00" - timeStringGmt2 = "2017-03-21T08:34:56+00:00" - fmt = "yyyy-MM-dd'T'HH:mm:sszzz" - # XXX Check which one is the right solution here: +let a = fromUnix(1_000_000_000) +let b = fromUnix(1_500_000_000) +doAssert b - a == 500_000_000 - let x = $timeString.parse(fmt).toTime().getGMTime() - doAssert x == timeStringGmt or x == timeStringGmt2 +# Because we can't change the timezone JS uses, we define a simple static timezone for testing. + +proc staticZoneInfoFromUtc(time: Time): ZonedTime = + result.utcOffset = -7200 + result.isDst = false + result.adjTime = (time.toUnix + 7200).Time + +proc staticZoneInfoFromTz(adjTime: Time): ZonedTIme = + result.utcOffset = -7200 + result.isDst = false + result.adjTime = adjTime + +let utcPlus2 = Timezone(zoneInfoFromUtc: staticZoneInfoFromUtc, zoneInfoFromTz: staticZoneInfoFromTz, name: "") + +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.inZone(utcPlus2) == $dt + +doAssert $initDateTime(01, mJan, 1911, 12, 00, 00, utc()) == "1911-01-01T12:00:00+00:00" +# See #6752 +# doAssert $initDateTime(01, mJan, 1900, 12, 00, 00, utc()) == "0023-01-01T12:00:00+00:00" \ No newline at end of file diff --git a/tests/metatype/ttypedesc2.nim b/tests/metatype/ttypedesc2.nim index 7650a6f6b4..4b6cfe6bc7 100644 --- a/tests/metatype/ttypedesc2.nim +++ b/tests/metatype/ttypedesc2.nim @@ -1,5 +1,5 @@ discard """ - output: "(x: a)" + output: '''(x: 'a')''' """ type diff --git a/tests/misc/tevents.nim b/tests/misc/tevents.nim index 5f2169f29a..caf674084b 100644 --- a/tests/misc/tevents.nim +++ b/tests/misc/tevents.nim @@ -8,19 +8,19 @@ HandlePrintEvent2: Output -> printing for ME''' import events type - TPrintEventArgs = object of TEventArgs + PrintEventArgs = object of EventArgs user*: string -proc handleprintevent*(e: TEventArgs) = +proc handleprintevent*(e: EventArgs) = write(stdout, "HandlePrintEvent: Output -> Handled print event\n") -proc handleprintevent2*(e: TEventArgs) = - var args: TPrintEventArgs = TPrintEventArgs(e) +proc handleprintevent2*(e: EventArgs) = + var args: PrintEventArgs = PrintEventArgs(e) write(stdout, "HandlePrintEvent2: Output -> printing for " & args.user) var ee = initEventEmitter() -var eventargs: TPrintEventArgs +var eventargs: PrintEventArgs eventargs.user = "ME\n" ##method one test @@ -33,16 +33,16 @@ ee.emit("print", eventargs) ##method two test type - TSomeObject = object of TObject - PrintEvent: TEventHandler + SomeObject = object of RootObj + printEvent: EventHandler -var obj: TSomeObject -obj.PrintEvent = initEventHandler("print") -obj.PrintEvent.addHandler(handleprintevent2) +var obj: SomeObject +obj.printEvent = initEventHandler("print") +obj.printEvent.addHandler(handleprintevent2) -ee.emit(obj.PrintEvent, eventargs) +ee.emit(obj.printEvent, eventargs) -obj.PrintEvent.removeHandler(handleprintevent2) +obj.printEvent.removeHandler(handleprintevent2) -ee.emit(obj.PrintEvent, eventargs) +ee.emit(obj.printEvent, eventargs) diff --git a/tests/misc/tlocals.nim b/tests/misc/tlocals.nim index 3e240d3c87..09b7432f57 100644 --- a/tests/misc/tlocals.nim +++ b/tests/misc/tlocals.nim @@ -1,5 +1,5 @@ discard """ - output: "(x: string here, a: 1)" + output: '''(x: "string here", a: 1)''' """ proc simple[T](a: T) = diff --git a/tests/modules/mrange.nim b/tests/modules/mrange.nim new file mode 100644 index 0000000000..20c424a8c7 --- /dev/null +++ b/tests/modules/mrange.nim @@ -0,0 +1,5 @@ + +proc range*() = echo "yo" + +proc set*(a: int) = + discard diff --git a/tests/modules/tambig_range.nim b/tests/modules/tambig_range.nim new file mode 100644 index 0000000000..0103505210 --- /dev/null +++ b/tests/modules/tambig_range.nim @@ -0,0 +1,13 @@ +discard """ + errormsg: "ambiguous identifier: 'range' --use system.range or mrange.range" + line: 13 +""" + +import mrange + +# bug #6965 +type SomeObj = object + s: set[int8] + +# bug #6726 +range() diff --git a/tests/objects/tobjconstr.nim b/tests/objects/tobjconstr.nim index 12478f6218..d1f3c8bdb7 100644 --- a/tests/objects/tobjconstr.nim +++ b/tests/objects/tobjconstr.nim @@ -1,21 +1,22 @@ discard """ - output: '''(k: kindA, a: (x: abc, z: [1, 1, 3]), method: ()) -(k: kindA, a: (x: abc, z: [1, 2, 3]), method: ()) -(k: kindA, a: (x: abc, z: [1, 3, 3]), method: ()) -(k: kindA, a: (x: abc, z: [1, 4, 3]), method: ()) -(k: kindA, a: (x: abc, z: [1, 5, 3]), method: ()) -(k: kindA, a: (x: abc, z: [1, 6, 3]), method: ()) -(k: kindA, a: (x: abc, z: [1, 7, 3]), method: ()) -(k: kindA, a: (x: abc, z: [1, 8, 3]), method: ()) -(k: kindA, a: (x: abc, z: [1, 9, 3]), method: ()) -(k: kindA, a: (x: abc, z: [1, 10, 3]), method: ()) + output: '''(k: kindA, a: (x: "abc", z: [1, 1, 3]), method: ()) +(k: kindA, a: (x: "abc", z: [1, 2, 3]), method: ()) +(k: kindA, a: (x: "abc", z: [1, 3, 3]), method: ()) +(k: kindA, a: (x: "abc", z: [1, 4, 3]), method: ()) +(k: kindA, a: (x: "abc", z: [1, 5, 3]), method: ()) +(k: kindA, a: (x: "abc", z: [1, 6, 3]), method: ()) +(k: kindA, a: (x: "abc", z: [1, 7, 3]), method: ()) +(k: kindA, a: (x: "abc", z: [1, 8, 3]), method: ()) +(k: kindA, a: (x: "abc", z: [1, 9, 3]), method: ()) +(k: kindA, a: (x: "abc", z: [1, 10, 3]), method: ()) (x: 123) (x: 123) (z: 89, y: 0, x: 128) (y: 678, x: 123) (y: 678, x: 123) (y: 0, x: 123) -(y: 678, x: 123)''' +(y: 678, x: 123) +(y: 123, x: 678)''' """ type @@ -75,3 +76,7 @@ when true: echo b # (y: 0, x: 123) b=B(y: 678, x: 123) echo b # (y: 678, x: 123) + b=B(y: b.x, x: b.y) + echo b # (y: 123, x: 678) + +GC_fullCollect() diff --git a/tests/objects/tobjconstr2.nim b/tests/objects/tobjconstr2.nim index f6805190bb..6253edab00 100644 --- a/tests/objects/tobjconstr2.nim +++ b/tests/objects/tobjconstr2.nim @@ -1,3 +1,8 @@ +discard """ + output: '''42 +Foo''' +""" + type TFoo{.exportc.} = object x:int @@ -48,3 +53,5 @@ type NamedGraphic = object of Graphic2 var ngr = NamedGraphic(kind: Koo, radius: 6.9, name: "Foo") echo ngr.name + +GC_fullCollect() diff --git a/tests/pragmas/custom_pragma.nim b/tests/pragmas/custom_pragma.nim new file mode 100644 index 0000000000..9e8f51deba --- /dev/null +++ b/tests/pragmas/custom_pragma.nim @@ -0,0 +1,5 @@ +# imported by tcustom_pragmas to test scoping + +template serializationKey*(s: string) {.pragma.} +template defaultValue*(V: typed) {.pragma.} +template alternativeKey*(s: string = nil, V: typed) {.pragma.} \ No newline at end of file diff --git a/tests/pragmas/tcustom_pragma.nim b/tests/pragmas/tcustom_pragma.nim new file mode 100644 index 0000000000..a2380522ff --- /dev/null +++ b/tests/pragmas/tcustom_pragma.nim @@ -0,0 +1,43 @@ +import macros + +block: + template myAttr() {.pragma.} + + proc myProc():int {.myAttr.} = 2 + const myAttrIdx = myProc.hasCustomPragma(myAttr) + static: + assert(myAttrIdx) + +block: + template myAttr(a: string) {.pragma.} + + type MyObj = object + myField1, myField2 {.myAttr: "hi".}: int + var o: MyObj + static: + assert o.myField2.hasCustomPragma(myAttr) + assert(not o.myField1.hasCustomPragma(myAttr)) + +import custom_pragma +block: # A bit more advanced case + type + Subfield = object + c {.serializationKey: "cc".}: float + + MySerializable = object + a {.serializationKey"asdf", defaultValue: 5.} : int + b {.custom_pragma.defaultValue"hello".} : int + field: Subfield + d {.alternativeKey("df", 5).}: float + e {.alternativeKey(V = 5).}: seq[bool] + + var s: MySerializable + + const aDefVal = s.a.getCustomPragmaVal(defaultValue) + static: assert(aDefVal == 5) + + const aSerKey = s.a.getCustomPragmaVal(serializationKey) + static: assert(aSerKey == "asdf") + + const cSerKey = getCustomPragmaVal(s.field.c, serializationKey) + static: assert(cSerKey == "cc") diff --git a/tests/pragmas/tnoreturn.nim b/tests/pragmas/tnoreturn.nim new file mode 100644 index 0000000000..4d00c60346 --- /dev/null +++ b/tests/pragmas/tnoreturn.nim @@ -0,0 +1,22 @@ +discard """ +ccodeCheck: "\\i @'__attribute__((noreturn))' .*" +""" + +proc noret1*(i: int) {.noreturn.} = + echo i + + +proc noret2*(i: int): void {.noreturn.} = + echo i + +var p {.used.}: proc(i: int): int +doAssert(not compiles( + p = proc(i: int): int {.noreturn.} = i # noreturn lambda returns int +)) + + +doAssert(not compiles( + block: + noret1(5) + echo 1 # statement after noreturn +)) diff --git a/tests/showoff/thello2.nim b/tests/showoff/thello2.nim index d2e2f62271..3ccb4e3be1 100644 --- a/tests/showoff/thello2.nim +++ b/tests/showoff/thello2.nim @@ -1,5 +1,5 @@ discard """ - output: '''(a: 3, b: 4, s: abc)''' + output: '''(a: 3, b: 4, s: "abc")''' """ type diff --git a/tests/stdlib/somesql.sql b/tests/stdlib/somesql.sql new file mode 100644 index 0000000000..285f93cec3 --- /dev/null +++ b/tests/stdlib/somesql.sql @@ -0,0 +1,298 @@ +create table anon40( + anon41 anon42 primary key default anon43(), + anon44 text unique not null, + anon45 text unique not null, + anon46 text not null, + anon47 text not null, + anon48 text default null, + anon49 text default null, + anon50 text default null, + anon51 text default null, + anon52 text default null, + anon53 text default null, + anon54 text default null, + anon55 text default null, + anon56 text default null, + anon57 text default null, + anon58 text default null, + anon59 text default null, + anon60 text default null, + anon61 text default null, + anon62 varchar(30) default null, + anon63 varchar(30) default null); +create table anon64( + anon41 serial primary key, + anon65 varchar(30) not null unique, + anon46 varchar(30) not null, + anon66 varchar(30) not null, + anon47 varchar(30) not null, + anon67 text not null, + anon55 varchar(30) not null unique, + anon68 varchar(30) default 'o', + anon69 boolean default true, + anon70 int not null references anon40(anon41)); +create table anon71( + anon72 varchar(30) not null primary key, + anon73 varchar(30) not null unique, + anon70 int not null references anon40(anon41)); +create table anon74( + anon72 varchar(30) not null primary key, + anon73 varchar(30) not null unique, + anon75 varchar(30) not null, + anon70 int not null references anon40(anon41), + foreign key(anon75) references anon71(anon72)); +create table anon76( + anon41 serial primary key, + anon72 varchar(30) not null unique, + anon73 varchar(30) not null unique, + anon77 varchar(30) not null, + anon70 int not null references anon40(anon41), + foreign key(anon77) references anon74(anon72)); +create table anon78( + anon41 serial primary key, + anon72 varchar(30) not null unique, + anon73 varchar(30) not null unique, + anon79 int not null, + anon80 varchar(30) default null, + anon81 int not null, + anon69 boolean not null default true, + anon70 int not null references anon40(anon41), + foreign key(anon79) references anon78(anon41), + foreign key(anon81) references anon76(anon41)); +create table anon82( + anon41 serial primary key, + anon72 varchar(30) not null unique, + anon73 text not null unique, + anon79 int not null, + anon80 text default null, + anon83 varchar(30) not null default 'd', + anon84 decimal default 0.00, + anon69 boolean not null default true, + anon85 decimal default 0.00, + anon86 decimal default 0.00, + anon87 decimal default 0.00, + anon70 int not null references anon40(anon41), + foreign key(anon79) references anon78(anon41)); +create table anon88( + anon41 serial primary key, + anon72 varchar(30) not null unique, + anon80 text default '', + anon69 boolean not null default true, + anon70 int not null references anon40(anon41)); +create table anon89( + anon90 int not null primary key, + anon91 anon92 default 0.00, + anon93 varchar(30), + anon69 boolean not null default true, + anon70 int not null references anon40(anon41), + foreign key(anon90) references anon82(anon41)); +create table anon94( + anon41 serial primary key, + anon73 text unique not null, + anon80 text default null, + anon69 boolean not null default true, + anon70 int not null references anon40(anon41)); +create table anon95( + anon41 serial primary key, + anon73 text unique not null, + anon96 int not null references anon94(anon41), + anon80 text default null, + anon69 boolean not null default true, + anon70 int not null references anon40(anon41)); +create table anon97( + anon41 serial primary key, + anon73 text unique not null, + anon98 int not null references anon95(anon41), + anon80 text default null, + anon69 boolean not null default true, + anon70 int not null references anon40(anon41)); +create table anon99( + anon41 serial primary key, + anon73 varchar(30) unique not null, + anon100 varchar(30) default null, + anon101 anon102 default 0, + anon103 varchar(30) default 'g', + anon104 int not null, + anon105 decimal not null default 1, + anon69 boolean not null default true, + anon70 int not null references anon40(anon41)); +create table anon106( + anon107 varchar(30) default 'g', + anon108 int references anon99(anon41) not null, + anon109 decimal default 1, + anon110 int references anon99(anon41) not null, + anon70 int not null references anon40(anon41)); +create table anon111( + anon41 serial primary key, + anon112 text unique not null, + anon73 text unique not null, + anon113 anon102 references anon97(anon41) not null, + anon114 varchar(30) not null, + anon115 int not null references anon88(anon41), + anon116 int not null references anon82(anon41), + anon117 int not null references anon82(anon41), + anon118 int not null references anon82(anon41), + anon119 int not null references anon82(anon41), + anon120 int not null references anon82(anon41), + anon121 int not null references anon82(anon41), + anon122 int references anon99(anon41) not null, + anon123 decimal default 0.00, + anon124 decimal default 0.00, + anon69 boolean default true, + anon70 int not null references anon40(anon41)); +create table anon125( + anon41 serial primary key, + anon126 int references anon111(anon41) not null, + anon80 text not null, + anon127 varchar(30) not null, + anon128 decimal default 0.00, + anon129 decimal default 0, + anon130 decimal default 0, + anon131 decimal default 0, + anon132 decimal default 0, + anon133 decimal default 0.00, + anon134 decimal default 0.00, + anon135 decimal default 0.00, + anon70 int not null references anon40(anon41), constraint anon136 check anon137(anon126, anon127, anon129)); +create table anon138( + anon41 serial primary key, + anon126 int references anon111(anon41) not null, + anon80 text not null, + anon127 varchar(30) not null, + anon139 date not null, + anon129 decimal default 0, + anon130 decimal default 0, + anon131 decimal default 0, + anon132 decimal default 0, + anon70 int not null references anon40(anon41), constraint anon136 check anon137(anon127, anon129)); +create table anon140( + anon41 serial primary key, + anon141 text unique not null, + anon46 text default null, + anon47 text default null, + anon57 varchar(30) default null, + anon142 text default null, + anon51 text default null, + anon143 varchar(30) default null, + anon53 text default null, + anon54 text default null, + anon55 text default null, + anon45 text default null, + anon69 boolean default true, + anon70 int not null references anon40(anon41)); +create table anon144( + anon41 serial primary key, + anon72 varchar(30) unique not null, + anon73 varchar(30) unique not null, + anon80 varchar(30) default null, + anon69 boolean default true, + anon70 int not null references anon40(anon41)); +create table anon145( + anon41 serial primary key, + anon72 varchar(30) unique not null, + anon73 varchar(30) unique not null, + anon146 int not null, + anon147 anon92 default 1, + anon148 anon92 default 9999999, + anon80 varchar(30) default null, + anon69 boolean default true, + anon149 int default 0, + anon150 int not null, + anon151 anon92 default 0, + anon70 int not null references anon40(anon41), + foreign key(anon150) references anon82(anon41), + foreign key(anon146) references anon144(anon41)); +create table anon152( + anon41 serial primary key, + anon73 varchar(30) not null unique, + anon153 varchar(30) not null unique, + anon80 text default null, + anon69 boolean not null default true, + anon70 int not null references anon40(anon41)); +create table anon154( + anon41 serial primary key not null, + anon155 int not null unique, + date date default anon156 not null, + anon157 anon102 references anon140(anon41) not null, + anon158 anon102 references anon64(anon41) not null, + anon159 decimal default 0 not null, + anon160 decimal default 0 not null, + anon161 decimal default 0 not null, + anon162 decimal default 0 not null, + anon163 decimal default 0 not null, + anon164 decimal default 0 not null, + anon165 decimal default 0.00, + anon166 decimal default 0 not null, + anon167 decimal default 0.00, + anon168 decimal default 0 not null, + anon169 boolean default false, + anon170 varchar(30) default 'ca', + anon171 varchar(30) default 'n', + anon172 varchar(30) not null default 'd', + anon173 decimal default 0.00, + anon174 decimal default 0.00, + anon175 int, + anon176 varchar(30) default null, + anon177 varchar(30) default '', + anon178 varchar(30) default null, + anon70 int not null references anon40(anon41)); +create table anon179( + anon41 serial primary key not null, + anon180 anon102 references anon154(anon41) not null, + anon181 int references anon125(anon41) not null, + anon182 int references anon82(anon41) not null, + anon122 int references anon99(anon41) not null, + anon183 decimal not null, + anon184 decimal default 0.00, + anon174 decimal default 0, + anon160 decimal default 0.00, + anon185 decimal default 0, + anon162 decimal default 0.00, + anon186 decimal default 0, + anon163 decimal default 0.00, + anon187 decimal default 0, + anon164 decimal default 0.00, + anon188 decimal default 0, + anon161 decimal default 0.00, + anon189 decimal default 0.00, + anon168 decimal default 0.00, + anon190 decimal not null, + anon191 decimal default 0, + anon83 varchar(30) not null default 't', + anon192 decimal default 0, + anon193 decimal not null, + anon194 decimal not null, + anon70 int not null references anon40(anon41)); +create table anon195( + anon41 serial not null, + anon196 int not null, + anon175 char not null, + anon90 int not null references anon82, + anon165 decimal default 0.00, + anon70 int not null references anon40(anon41), primary key(anon196, anon175)); +create table anon197( + anon41 serial not null, + anon196 int not null, + anon175 char not null, + anon198 int not null, + anon189 decimal default 0.00, + anon199 varchar(30) default null, + anon200 varchar(30) default null, + anon70 int not null references anon40(anon41), + primary key(anon196, anon175), + foreign key(anon198) references anon145(anon41)); +create table anon201( + anon41 serial primary key, + anon202 varchar(30) not null, + anon203 varchar(30) not null, + anon204 varchar(30) not null, + anon205 varchar(30) not null, + anon206 boolean default null, + anon70 int not null references anon40(anon41)); +create table anon207( + anon41 serial primary key, + anon208 varchar(30) not null, + anon209 varchar(30) not null, + anon204 varchar(30) default null, + anon70 int not null references anon40(anon41)); + diff --git a/tests/stdlib/tfrexp1.nim b/tests/stdlib/tfrexp1.nim new file mode 100644 index 0000000000..c6bb2b38cc --- /dev/null +++ b/tests/stdlib/tfrexp1.nim @@ -0,0 +1,44 @@ +discard """ + targets: "js c c++" + output: '''ok''' +""" + +import math +import strformat + +const manualTest = false + +proc frexp_test(lo, hi, step: float64) = + var exp: int + var frac: float64 + + var eps = 1e-15.float64 + + var x:float64 = lo + while x <= hi: + frac = frexp(x.float, exp) + let rslt = pow(2.0, float(exp)) * frac + + doAssert(abs(rslt - x) < eps) + + when manualTest: + echo fmt("x: {x:10.3f} exp: {exp:4d} frac: {frac:24.20f} check: {$(abs(rslt - x) < eps):-5s} {rslt: 9.3f}") + x += step + +when manualTest: + var exp: int + var frac: float64 + + for flval in [1.7976931348623157e+308, -1.7976931348623157e+308, # max, min float64 + 3.4028234663852886e+38, -3.4028234663852886e+38, # max, min float32 + 4.9406564584124654e-324, -4.9406564584124654e-324, # smallest/largest positive/negative float64 + 1.4012984643248171e-45, -1.4012984643248171e-45, # smallest/largest positive/negative float32 + 2.2250738585072014e-308, 1.1754943508222875e-38]: # smallest normal float64/float32 + frac = frexp(flval, exp) + echo fmt("{flval:25.16e}, {exp: 6d}, {frac: .20f} {frac * pow(2.0, float(exp)): .20e}") + + frexp_test(-1000.0, 1000.0, 0.0125) +else: + frexp_test(-1000000.0, 1000000.0, 0.125) + +echo "ok" diff --git a/tests/stdlib/tlists.nim b/tests/stdlib/tlists.nim index 4caa05c90d..37e73c53fa 100644 --- a/tests/stdlib/tlists.nim +++ b/tests/stdlib/tlists.nim @@ -17,7 +17,7 @@ block SinglyLinkedListTest1: block SinglyLinkedListTest2: var L: TSinglyLinkedList[string] for d in items(data): L.prepend($d) - assert($L == "[6, 5, 4, 3, 2, 1]") + assert($L == """["6", "5", "4", "3", "2", "1"]""") assert("4" in L) diff --git a/tests/stdlib/tmemfiles2.nim b/tests/stdlib/tmemfiles2.nim index 665e92e8a2..7ea94cffc5 100644 --- a/tests/stdlib/tmemfiles2.nim +++ b/tests/stdlib/tmemfiles2.nim @@ -1,10 +1,8 @@ discard """ file: "tmemfiles2.nim" - disabled: true output: '''Full read size: 20 Half read size: 10 Data: Hello''' """ -# doesn't work on windows. fmReadWrite doesn't create a file. import memfiles, os var mm, mm_full, mm_half: MemFile diff --git a/tests/stdlib/tparsesql.nim b/tests/stdlib/tparsesql.nim new file mode 100644 index 0000000000..3dc949ea1c --- /dev/null +++ b/tests/stdlib/tparsesql.nim @@ -0,0 +1,191 @@ +discard """ + file: "tparsesql.nim" +""" + +import parsesql + +doAssert $parseSQL("SELECT foo FROM table;") == "select foo from table;" +doAssert $parseSQL(""" +SELECT + CustomerName, + ContactName, + Address, + City, + PostalCode, + Country, + CustomerName, + ContactName, + Address, + City, + PostalCode, + Country +FROM table;""") == "select CustomerName, ContactName, Address, City, PostalCode, Country, CustomerName, ContactName, Address, City, PostalCode, Country from table;" + +doAssert $parseSQL("SELECT foo FROM table limit 10") == "select foo from table limit 10;" +doAssert $parseSQL("SELECT foo, bar, baz FROM table limit 10") == "select foo, bar, baz from table limit 10;" +doAssert $parseSQL("SELECT foo AS bar FROM table") == "select foo as bar from table;" +doAssert $parseSQL("SELECT foo AS foo_prime, bar AS bar_prime, baz AS baz_prime FROM table") == "select foo as foo_prime, bar as bar_prime, baz as baz_prime from table;" +doAssert $parseSQL("SELECT * FROM table") == "select * from table;" + + +#TODO add count(*) +#doAssert $parseSQL("SELECT COUNT(*) FROM table" + +doAssert $parseSQL(""" +SELECT * FROM table +WHERE a = b and c = d +""") == "select * from table where a = b and c = d;" + +doAssert $parseSQL(""" +SELECT * FROM table +WHERE not b +""") == "select * from table where not b;" + +doAssert $parseSQL(""" +SELECT + * +FROM + table +WHERE + a and not b +""") == "select * from table where a and not b;" + +doAssert $parseSQL(""" +SELECT * FROM table +WHERE a = b and c = d or n is null and not b + 1 = 3 +""") == "select * from table where a = b and c = d or n is null and not b + 1 = 3;" + +doAssert $parseSQL(""" +SELECT * FROM table +WHERE (a = b and c = d) or (n is null and not b + 1 = 3) +""") == "select * from table where(a = b and c = d) or (n is null and not b + 1 = 3);" + +doAssert $parseSQL(""" +SELECT * FROM table +HAVING a = b and c = d +""") == "select * from table having a = b and c = d;" + +doAssert $parseSQL(""" +SELECT a, b FROM table +GROUP BY a +""") == "select a, b from table group by a;" + +doAssert $parseSQL(""" +SELECT a, b FROM table +GROUP BY 1, 2 +""") == "select a, b from table group by 1, 2;" + +doAssert $parseSQL("SELECT t.a FROM t as t") == "select t.a from t as t;" + +doAssert $parseSQL(""" +SELECT a, b FROM ( + SELECT * FROM t +) +""") == "select a, b from(select * from t);" + +doAssert $parseSQL(""" +SELECT a, b FROM ( + SELECT * FROM t +) as foo +""") == "select a, b from(select * from t) as foo;" + +doAssert $parseSQL(""" +SELECT a, b FROM ( + SELECT * FROM ( + SELECT * FROM ( + SELECT * FROM ( + SELECT * FROM innerTable as inner1 + ) as inner2 + ) as inner3 + ) as inner4 +) as inner5 +""") == "select a, b from(select * from(select * from(select * from(select * from innerTable as inner1) as inner2) as inner3) as inner4) as inner5;" + +doAssert $parseSQL(""" +SELECT a, b FROM + (SELECT * FROM a), + (SELECT * FROM b), + (SELECT * FROM c) +""") == "select a, b from(select * from a),(select * from b),(select * from c);" + +doAssert $parseSQL(""" +SELECT * FROM Products +WHERE Price BETWEEN 10 AND 20; +""") == "select * from Products where Price between 10 and 20;" + +doAssert $parseSQL(""" +SELECT id FROM a +JOIN b +ON a.id == b.id +""") == "select id from a join b on a.id == b.id;" + +doAssert $parseSQL(""" +SELECT id FROM a +JOIN (SELECT id from c) as b +ON a.id == b.id +""") == "select id from a join(select id from c) as b on a.id == b.id;" + +doAssert $parseSQL(""" +SELECT id FROM a +INNER JOIN b +ON a.id == b.id +""") == "select id from a inner join b on a.id == b.id;" + +doAssert $parseSQL(""" +SELECT id FROM a +OUTER JOIN b +ON a.id == b.id +""") == "select id from a outer join b on a.id == b.id;" + +doAssert $parseSQL(""" +SELECT id FROM a +CROSS JOIN b +ON a.id == b.id +""") == "select id from a cross join b on a.id == b.id;" + +doAssert $parseSQL(""" +CREATE TYPE happiness AS ENUM ('happy', 'very happy', 'ecstatic'); +CREATE TABLE holidays ( + num_weeks int, + happiness happiness +); +CREATE INDEX table1_attr1 ON table1(attr1); +SELECT * FROM myTab WHERE col1 = 'happy'; +""") == "create type happiness as enum ('happy' , 'very happy' , 'ecstatic' ); create table holidays(num_weeks int , happiness happiness );; create index table1_attr1 on table1(attr1 );; select * from myTab where col1 = 'happy';" + +doAssert $parseSQL(""" +INSERT INTO Customers (CustomerName, ContactName, Address, City, PostalCode, Country) +VALUES ('Cardinal', 'Tom B. Erichsen', 'Skagen 21', 'Stavanger', '4006', 'Norway'); +""") == "insert into Customers (CustomerName , ContactName , Address , City , PostalCode , Country ) values ('Cardinal' , 'Tom B. Erichsen' , 'Skagen 21' , 'Stavanger' , '4006' , 'Norway' );" + +doAssert $parseSQL(""" +INSERT INTO TableName DEFAULT VALUES +""") == "insert into TableName default values;" + +doAssert $parseSQL(""" +UPDATE Customers +SET ContactName = 'Alfred Schmidt', City= 'Frankfurt' +WHERE CustomerID = 1; +""") == "update Customers set ContactName = 'Alfred Schmidt' , City = 'Frankfurt' where CustomerID = 1;" + +doAssert $parseSQL("DELETE FROM table_name;") == "delete from table_name;" + +doAssert $parseSQL("DELETE * FROM table_name;") == "delete from table_name;" + +doAssert $parseSQL(""" +--Select all: +SELECT * FROM Customers; +""") == "select * from Customers;" + +doAssert $parseSQL(""" +SELECT * FROM Customers WHERE (CustomerName LIKE 'L%' +OR CustomerName LIKE 'R%' /*OR CustomerName LIKE 'S%' +OR CustomerName LIKE 'T%'*/ OR CustomerName LIKE 'W%') +AND Country='USA' +ORDER BY CustomerName; +""") == "select * from Customers where(CustomerName like 'L%' or CustomerName like 'R%' or CustomerName like 'W%') and Country = 'USA' order by CustomerName;" + +# parse keywords as identifires +doAssert $parseSQL(""" +SELECT `SELECT`, `FROM` as `GROUP` FROM `WHERE`; +""") == """select "SELECT", "FROM" as "GROUP" from "WHERE";""" diff --git a/tests/stdlib/treloop.nim b/tests/stdlib/treloop.nim index 35236708cb..b4221525d2 100644 --- a/tests/stdlib/treloop.nim +++ b/tests/stdlib/treloop.nim @@ -1,5 +1,5 @@ discard """ - output: "@[(, +, 1, 2, )]" + output: '''@["(", "+", " 1", " 2", ")"]''' """ import re diff --git a/tests/stdlib/tsqlparser.nim b/tests/stdlib/tsqlparser.nim new file mode 100644 index 0000000000..4a7b2f7d7f --- /dev/null +++ b/tests/stdlib/tsqlparser.nim @@ -0,0 +1,12 @@ +discard """ + output: '''true''' +""" + +# Just check that we can parse 'somesql' and render it without crashes. + +import parsesql, streams, os + +var tree = parseSql(newFileStream(getAppDir() / "somesql.sql"), "somesql") +discard renderSql(tree) + +echo "true" diff --git a/tests/stdlib/tstrformat.nim b/tests/stdlib/tstrformat.nim new file mode 100644 index 0000000000..4e5c614a78 --- /dev/null +++ b/tests/stdlib/tstrformat.nim @@ -0,0 +1,13 @@ +discard """ + action: "run" +""" + +import strformat + +type Obj = object + +proc `$`(o: Obj): string = "foobar" + +var o: Obj +doAssert fmt"{o}" == "foobar" +doAssert fmt"{o:10}" == "foobar " \ No newline at end of file diff --git a/tests/stdlib/tstring.nim b/tests/stdlib/tstring.nim index 904bc462a2..6607461500 100644 --- a/tests/stdlib/tstring.nim +++ b/tests/stdlib/tstring.nim @@ -56,4 +56,24 @@ proc test_string_slice() = echo("OK") +proc test_string_cmp() = + let world = "hello\0world" + let earth = "hello\0earth" + let short = "hello\0" + let hello = "hello" + let goodbye = "goodbye" + + doAssert world == world + doAssert world != earth + doAssert world != short + doAssert world != hello + doAssert world != goodbye + + doAssert cmp(world, world) == 0 + doAssert cmp(world, earth) > 0 + doAssert cmp(world, short) > 0 + doAssert cmp(world, hello) > 0 + doAssert cmp(world, goodbye) > 0 + test_string_slice() +test_string_cmp() diff --git a/tests/stdlib/ttimes.nim b/tests/stdlib/ttimes.nim index 84e00f8de1..1f8ae6a225 100644 --- a/tests/stdlib/ttimes.nim +++ b/tests/stdlib/ttimes.nim @@ -1,16 +1,17 @@ # test the new time module discard """ file: "ttimes.nim" - action: "run" + output: '''[Suite] ttimes +''' """ import - times, strutils + times, os, strutils, unittest # $ date --date='@2147483647' # Tue 19 Jan 03:14:07 GMT 2038 -proc checkFormat(t: TimeInfo, format, expected: string) = +proc checkFormat(t: DateTime, format, expected: string) = let actual = t.format(format) if actual != expected: echo "Formatting failure!" @@ -18,7 +19,7 @@ proc checkFormat(t: TimeInfo, format, expected: string) = echo "actual : ", actual doAssert false -let t = getGMTime(fromSeconds(2147483647)) +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" & @@ -27,107 +28,41 @@ t.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & t.checkFormat("yyyyMMddhhmmss", "20380119031407") -let t2 = getGMTime(fromSeconds(160070789)) # 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") -var t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997 +var t4 = fromUnix(876124714).utc # Mon 6 Oct 08:58:34 BST 1997 t4.checkFormat("M MM MMM MMMM", "10 10 Oct October") # Interval tests (t4 - initInterval(years = 2)).checkFormat("yyyy", "1995") (t4 - initInterval(years = 7, minutes = 34, seconds = 24)).checkFormat("yyyy mm ss", "1990 24 10") -proc parseTest(s, f, sExpected: string, ydExpected: int) = - let - parsed = s.parse(f) - parsedStr = $getGMTime(toTime(parsed)) - if parsedStr != sExpected: - echo "Parsing failure!" - echo "expected: ", sExpected - echo "actual : ", parsedStr - doAssert false - doAssert(parsed.yearday == ydExpected) -proc parseTestTimeOnly(s, f, sExpected: string) = - doAssert(sExpected in $s.parse(f)) - -# because setting a specific timezone for testing is platform-specific, we use -# explicit timezone offsets in all tests. - -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) -# 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) -# 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) -# 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 -# 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) -# 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 -# 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) -# 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 -# 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) -# 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) -# RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" -parseTest("2006-01-12T15:04:05.999999999Z-07:00", - "yyyy-MM-ddTHH:mm:ss.999999999Zzzz", "2006-01-12T22:04:05+00:00", 11) -for tzFormat in ["z", "zz", "zzz"]: - # formatting timezone as 'Z' for UTC - parseTest("2001-01-12T22:04:05Z", "yyyy-MM-dd'T'HH:mm:ss" & tzFormat, - "2001-01-12T22:04:05+00:00", 11) -# Kitchen = "3:04PM" -parseTestTimeOnly("3:04PM", "h:mmtt", "15:04:00") -#when not defined(testing): -# 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 - # checking dayOfWeek matches known days -doAssert getDayOfWeek(21, 9, 1900) == dFri -doAssert getDayOfWeek(1, 1, 1970) == dThu -doAssert getDayOfWeek(21, 9, 1970) == dMon -doAssert getDayOfWeek(1, 1, 2000) == dSat -doAssert getDayOfWeek(1, 1, 2021) == dFri -# Julian tests -doAssert getDayOfWeekJulian(21, 9, 1900) == dFri -doAssert getDayOfWeekJulian(21, 9, 1970) == dMon -doAssert getDayOfWeekJulian(1, 1, 2000) == dSat -doAssert getDayOfWeekJulian(1, 1, 2021) == dFri +doAssert getDayOfWeek(01, mJan, 0000) == dSat +doAssert getDayOfWeek(01, mJan, -0023) == dSat +doAssert getDayOfWeek(21, mSep, 1900) == dFri +doAssert getDayOfWeek(01, mJan, 1970) == dThu +doAssert getDayOfWeek(21, mSep, 1970) == dMon +doAssert getDayOfWeek(01, mJan, 2000) == dSat +doAssert getDayOfWeek(01, mJan, 2021) == dFri -# toSeconds tests with GM timezone -let t4L = getGMTime(fromSeconds(876124714)) -doAssert toSeconds(toTime(t4L)) == 876124714 -doAssert toSeconds(toTime(t4L)) + t4L.timezone.float == toSeconds(toTime(t4)) +# toUnix tests with GM timezone +let t4L = fromUnix(876124714).utc +doAssert toUnix(toTime(t4L)) == 876124714 +doAssert toUnix(toTime(t4L)) + t4L.utcOffset == toUnix(toTime(t4)) # adding intervals var - a1L = toSeconds(toTime(t4L + initInterval(hours = 1))) + t4L.timezone.float - a1G = toSeconds(toTime(t4)) + 60.0 * 60.0 + a1L = toUnix(toTime(t4L + initInterval(hours = 1))) + t4L.utcOffset + a1G = toUnix(toTime(t4)) + 60 * 60 doAssert a1L == a1G # subtracting intervals -a1L = toSeconds(toTime(t4L - initInterval(hours = 1))) + t4L.timezone.float -a1G = toSeconds(toTime(t4)) - (60.0 * 60.0) +a1L = toUnix(toTime(t4L - initInterval(hours = 1))) + t4L.utcOffset +a1G = toUnix(toTime(t4)) - (60 * 60) doAssert a1L == a1G # add/subtract TimeIntervals and Time/TimeInfo @@ -143,45 +78,16 @@ doAssert ti1 == getTime() ti1 += 1.days doAssert ti1 == getTime() + 1.days -# overflow of TimeIntervals on initalisation -doAssert initInterval(milliseconds = 25000) == initInterval(seconds = 25) -doAssert initInterval(seconds = 65) == initInterval(seconds = 5, minutes = 1) -doAssert initInterval(hours = 25) == initInterval(hours = 1, days = 1) -doAssert initInterval(months = 13) == initInterval(months = 1, years = 1) - # Bug with adding a day to a Time let day = 24.hours let tomorrow = getTime() + day doAssert tomorrow - getTime() == 60*60*24 -doAssert milliseconds(1000 * 60) == minutes(1) -doAssert milliseconds(1000 * 60 * 60) == hours(1) -doAssert milliseconds(1000 * 60 * 60 * 24) == days(1) -doAssert seconds(60 * 60) == hours(1) -doAssert seconds(60 * 60 * 24) == days(1) -doAssert seconds(60 * 60 + 65) == (hours(1) + minutes(1) + seconds(5)) - -# 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 -# are testing that be relying on the fact that tranforming a TimeInfo to a Time -# and back again will correctly set the DST value. With the incorrect parse -# behavior this will introduce a one hour offset from the named time and the -# parsed time if the DST value differs between the current time and the date we -# are parsing. -# -# Unfortunately these tests depend on the locale of the system in which they -# are run. They will not be meaningful when run in a locale without DST. They -# also assume that Jan. 1 and Jun. 1 will have differing isDST values. -let dstT1 = parse("2016-01-01 00:00:00", "yyyy-MM-dd HH:mm:ss") -let dstT2 = parse("2016-06-01 00:00:00", "yyyy-MM-dd HH:mm:ss") -doAssert dstT1 == getLocalTime(toTime(dstT1)) -doAssert dstT2 == getLocalTime(toTime(dstT2)) - # Comparison between Time objects should be detected by compiler # as 'noSideEffect'. proc cmpTimeNoSideEffect(t1: Time, t2: Time): bool {.noSideEffect.} = result = t1 == t2 -doAssert cmpTimeNoSideEffect(0.fromSeconds, 0.fromSeconds) +doAssert cmpTimeNoSideEffect(0.fromUnix, 0.fromUnix) # Additionally `==` generic for seq[T] has explicit 'noSideEffect' pragma # so we can check above condition by comparing seq[Time] sequences let seqA: seq[Time] = @[] @@ -195,49 +101,221 @@ for tz in [ (-1800, "+0", "+00", "+00:30"), # half an hour (7200, "-2", "-02", "-02:00"), # positive (38700, "-10", "-10", "-10:45")]: # positive with three quaters hour - let ti = TimeInfo(monthday: 1, timezone: tz[0]) + 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] -block formatDst: - var ti = TimeInfo(monthday: 1, isDst: true) - - # BST - ti.timezone = 0 - doAssert ti.format("z") == "+1" - doAssert ti.format("zz") == "+01" - doAssert ti.format("zzz") == "+01:00" - - # EDT - ti.timezone = 5 * 60 * 60 - doAssert ti.format("z") == "-4" - doAssert ti.format("zz") == "-04" - doAssert ti.format("zzz") == "-04:00" - -block dstTest: - let nonDst = TimeInfo(year: 2015, month: mJan, monthday: 01, yearday: 0, - weekday: dThu, hour: 00, minute: 00, second: 00, isDST: false, timezone: 0) - var dst = nonDst - dst.isDst = true - # note that both isDST == true and isDST == false are valid here because - # DST is in effect on January 1st in some southern parts of Australia. - # FIXME: Fails in UTC - # doAssert nonDst.toTime() - dst.toTime() == 3600 - doAssert nonDst.format("z") == "+0" - doAssert dst.format("z") == "+1" - - # parsing will set isDST in relation to the local time. We take a date in - # January and one in July to maximize the probability to hit one date with DST - # and one without on the local machine. However, this is not guaranteed. - let - 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) == fromSeconds(1451962800) - doAssert toTime(parsedJul) == fromSeconds(1467342000) - block countLeapYears: # 1920, 2004 and 2020 are leap years, and should be counted starting at the following year doAssert countLeapYears(1920) + 1 == countLeapYears(1921) doAssert countLeapYears(2004) + 1 == countLeapYears(2005) - doAssert countLeapYears(2020) + 1 == countLeapYears(2021) \ No newline at end of file + doAssert countLeapYears(2020) + 1 == countLeapYears(2021) + +block timezoneConversion: + var l = now() + let u = l.utc + l = u.local + + doAssert l.timezone == local() + doAssert u.timezone == utc() + +template parseTest(s, f, sExpected: string, ydExpected: int) = + let + parsed = s.parse(f, utc()) + parsedStr = $parsed + check parsedStr == sExpected + if parsed.yearday != ydExpected: + echo s + echo parsed.repr + echo parsed.yearday, " exp: ", ydExpected + check(parsed.yearday == ydExpected) + +template parseTestTimeOnly(s, f, sExpected: string) = + check sExpected in $s.parse(f, utc()) + +# because setting a specific timezone for testing is platform-specific, we use +# 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) + # 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) + # 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) + # 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 + # 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) + # 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 + # 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) + # 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 + # 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) + # 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) + # RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" + parseTest("2006-01-12T15:04:05.999999999Z-07:00", + "yyyy-MM-ddTHH:mm:ss.999999999Zzzz", "2006-01-12T22:04:05+00:00", 11) + for tzFormat in ["z", "zz", "zzz"]: + # formatting timezone as 'Z' for UTC + parseTest("2001-01-12T22:04:05Z", "yyyy-MM-dd'T'HH:mm:ss" & tzFormat, + "2001-01-12T22:04:05+00:00", 11) + # Kitchen = "3:04PM" + parseTestTimeOnly("3:04PM", "h:mmtt", "15:04:00") + #when not defined(testing): + # 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 + # are testing that be relying on the fact that tranforming a TimeInfo to a Time + # and back again will correctly set the DST value. With the incorrect parse + # behavior this will introduce a one hour offset from the named time and the + # parsed time if the DST value differs between the current time and the date we + # are parsing. + let dstT1 = parse("2016-01-01 00:00:00", "yyyy-MM-dd HH:mm:ss") + let dstT2 = parse("2016-06-01 00:00:00", "yyyy-MM-dd HH:mm:ss") + check dstT1 == toTime(dstT1).local + check dstT2 == toTime(dstT2).local + + block dstTest: + # parsing will set isDST in relation to the local time. We take a date in + # January and one in July to maximize the probability to hit one date with DST + # and one without on the local machine. However, this is not guaranteed. + let + 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) == fromUnix(1451962800) + doAssert toTime(parsedJul) == fromUnix(1467342000) + +suite "ttimes": + + # Generate tests for multiple timezone files where available + # Set the TZ env var for each test + when defined(Linux) or defined(macosx): + const tz_dir = "/usr/share/zoneinfo" + const f = "yyyy-MM-dd HH:mm zzz" + + let orig_tz = getEnv("TZ") + var tz_cnt = 0 + for tz_fn in walkFiles(tz_dir & "/*"): + if symlinkExists(tz_fn) or tz_fn.endsWith(".tab") or + tz_fn.endsWith(".list"): + continue + + test "test for " & tz_fn: + tz_cnt.inc + putEnv("TZ", tz_fn) + runTimezoneTests() + + test "enough timezone files tested": + check tz_cnt > 10 + + test "dst handling": + putEnv("TZ", "Europe/Stockholm") + # In case of an impossible time, the time is moved to after the impossible time period + check initDateTime(26, mMar, 2017, 02, 30, 00).format(f) == "2017-03-26 03:30 +02:00" + # In case of an ambiguous time, the earlier time is choosen + check initDateTime(29, mOct, 2017, 02, 00, 00).format(f) == "2017-10-29 02:00 +02:00" + # These are just dates on either side of the dst switch + check initDateTime(29, mOct, 2017, 01, 00, 00).format(f) == "2017-10-29 01:00 +02:00" + check initDateTime(29, mOct, 2017, 01, 00, 00).isDst + check initDateTime(29, mOct, 2017, 03, 01, 00).format(f) == "2017-10-29 03:01 +01:00" + check (not initDateTime(29, mOct, 2017, 03, 01, 00).isDst) + + check initDateTime(21, mOct, 2017, 01, 00, 00).format(f) == "2017-10-21 01:00 +02:00" + + test "issue #6520": + putEnv("TZ", "Europe/Stockholm") + var local = fromUnix(1469275200).local + var utc = fromUnix(1469275200).utc + + let claimedOffset = local.utcOffset + local.utcOffset = 0 + check claimedOffset == utc.toTime - local.toTime + + test "issue #5704": + putEnv("TZ", "Asia/Seoul") + let diff = parse("19700101-000000", "yyyyMMdd-hhmmss").toTime - parse("19000101-000000", "yyyyMMdd-hhmmss").toTime + check diff == 2208986872 + + test "issue #6465": + putEnv("TZ", "Europe/Stockholm") + let dt = parse("2017-03-25 12:00", "yyyy-MM-dd hh:mm") + check $(dt + 1.days) == "2017-03-26T12:00:00+02:00" + + test "datetime before epoch": + check $fromUnix(-2147483648).utc == "1901-12-13T20:45:52+00:00" + + test "adding/subtracting time across dst": + putenv("TZ", "Europe/Stockholm") + + let dt1 = initDateTime(26, mMar, 2017, 03, 00, 00) + check $(dt1 - 1.seconds) == "2017-03-26T01:59:59+01:00" + + var dt2 = initDateTime(29, mOct, 2017, 02, 59, 59) + check $(dt2 + 1.seconds) == "2017-10-29T02:00:00+01:00" + + putEnv("TZ", orig_tz) + + else: + # not on Linux or macosx: run one parseTest only + test "parseTest": + runTimezoneTests() + + test "dynamic timezone": + proc staticOffset(offset: int): Timezone = + proc zoneInfoFromTz(adjTime: Time): ZonedTime = + result.isDst = false + result.utcOffset = offset + result.adjTime = adjTime + + proc zoneInfoFromUtc(time: Time): ZonedTime = + result.isDst = false + result.utcOffset = offset + result.adjTime = fromUnix(time.toUnix - offset) + + result.name = "" + result.zoneInfoFromTz = zoneInfoFromTz + result.zoneInfoFromUtc = zoneInfoFromUtc + + let tz = staticOffset(-9000) + let dt = initDateTime(1, mJan, 2000, 12, 00, 00, tz) + 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.inZone(tz) == $dt + + test "isLeapYear": + check isLeapYear(2016) + check (not isLeapYear(2015)) + check isLeapYear(2000) + check (not isLeapYear(1900)) + + test "subtract months": + var dt = initDateTime(1, mFeb, 2017, 00, 00, 00, utc()) + check $(dt - 1.months) == "2017-01-01T00:00:00+00:00" + dt = initDateTime(15, mMar, 2017, 00, 00, 00, utc()) + check $(dt - 1.months) == "2017-02-15T00:00:00+00:00" + dt = initDateTime(31, mMar, 2017, 00, 00, 00, utc()) + # This happens due to monthday overflow. It's consistent with Phobos. + check $(dt - 1.months) == "2017-03-03T00:00:00+00:00" \ No newline at end of file diff --git a/tests/stdlib/tunittest.nim b/tests/stdlib/tunittest.nim index e4a8018713..86b9fd0370 100644 --- a/tests/stdlib/tunittest.nim +++ b/tests/stdlib/tunittest.nim @@ -13,6 +13,8 @@ discard """ [Suite] bug #5784 +[Suite] test name filtering + ''' """ @@ -120,3 +122,39 @@ suite "bug #5784": field: int var obj: Obj check obj.isNil or obj.field == 0 + +when defined(testing): + suite "test name filtering": + test "test name": + check matchFilter("suite1", "foo", "") + check matchFilter("suite1", "foo", "foo") + check matchFilter("suite1", "foo", "::") + check matchFilter("suite1", "foo", "*") + check matchFilter("suite1", "foo", "::foo") + check matchFilter("suite1", "::foo", "::foo") + + test "test name - glob": + check matchFilter("suite1", "foo", "f*") + check matchFilter("suite1", "foo", "*oo") + check matchFilter("suite1", "12345", "12*345") + check matchFilter("suite1", "q*wefoo", "q*wefoo") + check false == matchFilter("suite1", "foo", "::x") + check false == matchFilter("suite1", "foo", "::x*") + check false == matchFilter("suite1", "foo", "::*x") + # overlap + check false == matchFilter("suite1", "12345", "123*345") + check matchFilter("suite1", "ab*c::d*e::f", "ab*c::d*e::f") + + test "suite name": + check matchFilter("suite1", "foo", "suite1::") + check false == matchFilter("suite1", "foo", "suite2::") + check matchFilter("suite1", "qwe::foo", "qwe::foo") + check matchFilter("suite1", "qwe::foo", "suite1::qwe::foo") + + test "suite name - glob": + check matchFilter("suite1", "foo", "::*") + check matchFilter("suite1", "foo", "*::*") + check matchFilter("suite1", "foo", "*::foo") + check false == matchFilter("suite1", "foo", "*ite2::") + check matchFilter("suite1", "q**we::foo", "q**we::foo") + check matchFilter("suite1", "a::b*c::d*e", "a::b*c::d*e") diff --git a/tests/system/toString.nim b/tests/system/toString.nim index 3e7fc7ddbd..ea9d6b05b8 100644 --- a/tests/system/toString.nim +++ b/tests/system/toString.nim @@ -4,12 +4,12 @@ discard """ 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"]) +doAssert """["", "foo", "bar"]""" == $(["", "foo", "bar"]) # bug #2395 let alphaSet: set[char] = {'a'..'c'} -doAssert "{a, b, c}" == $alphaSet +doAssert "{'a', 'b', 'c'}" == $alphaSet doAssert "2.3242" == $(2.3242) doAssert "2.982" == $(2.982) doAssert "123912.1" == $(123912.1) @@ -49,5 +49,5 @@ import strutils # array test let arr = ['H','e','l','l','o',' ','W','o','r','l','d','!','\0'] -doAssert $arr == "[H, e, l, l, o, , W, o, r, l, d, !, \0]" +doAssert $arr == "['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\\x00']" doAssert $cstring(unsafeAddr arr) == "Hello World!" diff --git a/tests/testament/categories.nim b/tests/testament/categories.nim index 5468fc309d..5c845fc5c4 100644 --- a/tests/testament/categories.nim +++ b/tests/testament/categories.nim @@ -15,58 +15,53 @@ const rodfilesDir = "tests/rodfiles" - nimcacheDir = rodfilesDir / "nimcache" -proc delNimCache() = - try: - removeDir(nimcacheDir) - except OSError: - echo "[Warning] could not delete: ", nimcacheDir +proc delNimCache(filename, options: string) = + for target in low(TTarget)..high(TTarget): + let dir = nimcacheDir(filename, options, target) + try: + removeDir(dir) + except OSError: + echo "[Warning] could not delete: ", dir proc runRodFiles(r: var TResults, cat: Category, options: string) = - template test(filename: untyped) = + template test(filename: string, clearCacheFirst=false) = + if clearCacheFirst: delNimCache(filename, options) testSpec r, makeTest(rodfilesDir / filename, options, cat, actionRun) - delNimCache() # test basic recompilation scheme: - test "hallo" + test "hallo", true test "hallo" when false: # test incremental type information: test "hallo2" - delNimCache() # test type converters: - test "aconv" + test "aconv", true test "bconv" - delNimCache() # test G, A, B example from the documentation; test init sections: - test "deada" + test "deada", true test "deada2" - delNimCache() when false: # test method generation: - test "bmethods" + test "bmethods", true test "bmethods2" - delNimCache() # test generics: - test "tgeneric1" + test "tgeneric1", true test "tgeneric2" - delNimCache() proc compileRodFiles(r: var TResults, cat: Category, options: string) = - template test(filename: untyped) = + template test(filename: untyped, clearCacheFirst=true) = + if clearCacheFirst: delNimCache(filename, options) testSpec r, makeTest(rodfilesDir / filename, options, cat) - delNimCache() # test DLL interfacing: - test "gtkex1" + test "gtkex1", true test "gtkex2" - delNimCache() # --------------------- DLL generation tests ---------------------------------- @@ -96,7 +91,7 @@ proc runBasicDLLTest(c, r: var TResults, cat: Category, options: string) = # posix relies on crappy LD_LIBRARY_PATH (ugh!): var libpath = getEnv"LD_LIBRARY_PATH".string # Temporarily add the lib directory to LD_LIBRARY_PATH: - putEnv("LD_LIBRARY_PATH", "tests/dll:" & libpath) + putEnv("LD_LIBRARY_PATH", "tests/dll" & (if libpath.len > 0: ":" & libpath else: "")) defer: putEnv("LD_LIBRARY_PATH", libpath) var nimrtlDll = DynlibFormat % "nimrtl" safeCopyFile("lib" / nimrtlDll, "tests/dll" / nimrtlDll) @@ -153,7 +148,7 @@ proc gcTests(r: var TResults, cat: Category, options: string) = test "gcbench" test "gcleak" test "gcleak2" - test "gctest" + testWithoutBoehm "gctest" testWithNone "gctest" test "gcleak3" test "gcleak4" diff --git a/tests/testament/htmlgen.nim b/tests/testament/htmlgen.nim index 05c24b2b58..bf26a956d2 100644 --- a/tests/testament/htmlgen.nim +++ b/tests/testament/htmlgen.nim @@ -9,54 +9,33 @@ ## HTML generator for the tester. -import cgi, backend, strutils, json, os +import cgi, backend, strutils, json, os, tables, times import "testamenthtml.templ" -proc generateTestRunTabListItemPartial(outfile: File, testRunRow: JsonNode, firstRow = false) = +proc generateTestResultPanelPartial(outfile: File, testResultRow: JsonNode) = let - # The first tab gets the bootstrap class for a selected tab - firstTabActiveClass = if firstRow: "active" - else: "" - commitId = htmlQuote testRunRow["commit"].str - hash = htmlQuote(testRunRow["commit"].str) - branch = htmlQuote(testRunRow["branch"].str) - machineId = htmlQuote testRunRow["machine"].str - machineName = htmlQuote(testRunRow["machine"].str) - - outfile.generateHtmlTabListItem( - firstTabActiveClass, - commitId, - machineId, - branch, - hash, - machineName - ) - -proc generateTestResultPanelPartial(outfile: File, testResultRow: JsonNode, onlyFailing = false) = - let - trId = htmlQuote(testResultRow["category"].str & "_" & testResultRow["name"].str) + trId = htmlQuote(testResultRow["category"].str & "_" & testResultRow["name"].str). + multiReplace({".": "_", " ": "_", ":": "_"}) name = testResultRow["name"].str.htmlQuote() category = testResultRow["category"].str.htmlQuote() target = testResultRow["target"].str.htmlQuote() action = testResultRow["action"].str.htmlQuote() result = htmlQuote testResultRow["result"].str - expected = htmlQuote testResultRow["expected"].str - gotten = htmlQuote testResultRow["given"].str + expected = testResultRow["expected"].str + gotten = testResultRow["given"].str timestamp = "unknown" - var panelCtxClass, textCtxClass, bgCtxClass, resultSign, resultDescription: string + var + panelCtxClass, textCtxClass, bgCtxClass: string + resultSign, resultDescription: string case result of "reSuccess": - if onlyFailing: - return panelCtxClass = "success" textCtxClass = "success" bgCtxClass = "success" resultSign = "ok" resultDescription = "PASS" of "reIgnored": - if onlyFailing: - return panelCtxClass = "info" textCtxClass = "info" bgCtxClass = "info" @@ -71,9 +50,7 @@ proc generateTestResultPanelPartial(outfile: File, testResultRow: JsonNode, only outfile.generateHtmlTestresultPanelBegin( trId, name, target, category, action, resultDescription, - timestamp, - result, resultSign, - panelCtxClass, textCtxClass, bgCtxClass + timestamp, result, resultSign, panelCtxClass, textCtxClass, bgCtxClass ) if expected.isNilOrWhitespace() and gotten.isNilOrWhitespace(): outfile.generateHtmlTestresultOutputNone() @@ -90,7 +67,7 @@ type totalCount, successCount, ignoredCount, failedCount: int successPercentage, ignoredPercentage, failedPercentage: BiggestFloat -proc allTestResults(): AllTests = +proc allTestResults(onlyFailing = false): AllTests = result.data = newJArray() for file in os.walkFiles("testresults/*.json"): let data = parseFile(file) @@ -98,69 +75,74 @@ proc allTestResults(): AllTests = echo "[ERROR] ignoring json file that is not an array: ", file else: for elem in data: - result.data.add elem let state = elem["result"].str + inc result.totalCount if state.contains("reSuccess"): inc result.successCount elif state.contains("reIgnored"): inc result.ignoredCount + if not onlyFailing or not(state.contains("reSuccess")): + result.data.add elem + result.successPercentage = 100 * + (result.successCount.toBiggestFloat / result.totalCount.toBiggestFloat) + result.ignoredPercentage = 100 * + (result.ignoredCount.toBiggestFloat / result.totalCount.toBiggestFloat) + result.failedCount = result.totalCount - + result.successCount - result.ignoredCount + result.failedPercentage = 100 * + (result.failedCount.toBiggestFloat / result.totalCount.toBiggestFloat) - result.totalCount = result.data.len - result.successPercentage = 100 * (result.successCount.toBiggestFloat() / result.totalCount.toBiggestFloat()) - result.ignoredPercentage = 100 * (result.ignoredCount.toBiggestFloat() / result.totalCount.toBiggestFloat()) - result.failedCount = result.totalCount - result.successCount - result.ignoredCount - result.failedPercentage = 100 * (result.failedCount.toBiggestFloat() / result.totalCount.toBiggestFloat()) - - -proc generateTestResultsPanelGroupPartial(outfile: File, allResults: JsonNode, onlyFailing = false) = +proc generateTestResultsPanelGroupPartial(outfile: File, allResults: JsonNode) = for testresultRow in allResults: - generateTestResultPanelPartial(outfile, testresultRow, onlyFailing) + generateTestResultPanelPartial(outfile, testresultRow) -proc generateTestRunTabContentPartial(outfile: File, allResults: AllTests, testRunRow: JsonNode, onlyFailing = false, firstRow = false) = +proc generateAllTestsContent(outfile: File, allResults: AllTests, + onlyFailing = false) = + if allResults.data.len < 1: return # Nothing to do if there is no data. + # Only results from one test run means that test run environment info is the + # same for all tests let - # The first tab gets the bootstrap classes for a selected and displaying tab content - firstTabActiveClass = if firstRow: " in active" - else: "" - commitId = htmlQuote testRunRow["commit"].str - hash = htmlQuote(testRunRow["commit"].str) - branch = htmlQuote(testRunRow["branch"].str) - machineId = htmlQuote testRunRow["machine"].str - machineName = htmlQuote(testRunRow["machine"].str) - os = htmlQuote("unknown_os") - cpu = htmlQuote("unknown_cpu") + firstRow = allResults.data[0] + commit = htmlQuote firstRow["commit"].str + branch = htmlQuote firstRow["branch"].str + machine = htmlQuote firstRow["machine"].str - outfile.generateHtmlTabPageBegin( - firstTabActiveClass, commitId, - machineId, branch, hash, machineName, os, cpu, + outfile.generateHtmlAllTestsBegin( + machine, commit, branch, allResults.totalCount, - allResults.successCount, formatBiggestFloat(allResults.successPercentage, ffDecimal, 2) & "%", - allResults.ignoredCount, formatBiggestFloat(allResults.ignoredPercentage, ffDecimal, 2) & "%", - allResults.failedCount, formatBiggestFloat(allResults.failedPercentage, ffDecimal, 2) & "%" + allResults.successCount, + formatBiggestFloat(allResults.successPercentage, ffDecimal, 2) & "%", + allResults.ignoredCount, + formatBiggestFloat(allResults.ignoredPercentage, ffDecimal, 2) & "%", + allResults.failedCount, + formatBiggestFloat(allResults.failedPercentage, ffDecimal, 2) & "%", + onlyFailing ) - generateTestResultsPanelGroupPartial(outfile, allResults.data, onlyFailing) - outfile.generateHtmlTabPageEnd() - -proc generateTestRunsHtmlPartial(outfile: File, allResults: AllTests, onlyFailing = false) = - # Iterating the results twice, get entire result set in one go - outfile.generateHtmlTabListBegin() - if allResults.data.len > 0: - generateTestRunTabListItemPartial(outfile, allResults.data[0], true) - outfile.generateHtmlTabListEnd() - - outfile.generateHtmlTabContentsBegin() - var firstRow = true - for testRunRow in allResults.data: - generateTestRunTabContentPartial(outfile, allResults, testRunRow, onlyFailing, firstRow) - if firstRow: - firstRow = false - outfile.generateHtmlTabContentsEnd() + generateTestResultsPanelGroupPartial(outfile, allResults.data) + outfile.generateHtmlAllTestsEnd() proc generateHtml*(filename: string, onlyFailing: bool) = + let + currentTime = getTime().getLocalTime() + timestring = htmlQuote format(currentTime, "yyyy-MM-dd HH:mm:ss 'UTC'zzz") var outfile = open(filename, fmWrite) outfile.generateHtmlBegin() - generateTestRunsHtmlPartial(outfile, allTestResults(), onlyFailing) + generateAllTestsContent(outfile, allTestResults(onlyFailing), onlyFailing) - outfile.generateHtmlEnd() + outfile.generateHtmlEnd(timestring) outfile.flushFile() close(outfile) + +proc dumpJsonTestResults*(prettyPrint, onlyFailing: bool) = + var + outfile = stdout + jsonString: string + + let results = allTestResults(onlyFailing) + if prettyPrint: + jsonString = results.data.pretty() + else: + jsonString = $ results.data + + outfile.writeLine(jsonString) diff --git a/tests/testament/specs.nim b/tests/testament/specs.nim index e8513ab24c..ac79e39429 100644 --- a/tests/testament/specs.nim +++ b/tests/testament/specs.nim @@ -116,7 +116,7 @@ proc specDefaults*(result: var TSpec) = result.maxCodeSize = 0 proc parseTargets*(value: string): set[TTarget] = - for v in value.normalize.split: + for v in value.normalize.splitWhitespace: case v of "c": result.incl(targetC) of "cpp", "c++": result.incl(targetCpp) @@ -192,7 +192,7 @@ proc parseSpec*(filename: string): TSpec = of "ccodecheck": result.ccodeCheck = e.value of "maxcodesize": discard parseInt(e.value, result.maxCodeSize) of "target", "targets": - for v in e.value.normalize.split: + for v in e.value.normalize.splitWhitespace: case v of "c": result.targets.incl(targetC) of "cpp", "c++": result.targets.incl(targetCpp) diff --git a/tests/testament/testamenthtml.templ b/tests/testament/testamenthtml.templ index f7477f3aa5..9190f370eb 100644 --- a/tests/testament/testamenthtml.templ +++ b/tests/testament/testamenthtml.templ @@ -29,7 +29,7 @@ */ /** - * + * * @param {number} index * @param {Element[]} elemArray * @param {executeForElement} executeOnItem @@ -69,17 +69,16 @@ } /** - * @param {string} tabId The id of the tabpanel div to search. * @param {string} [category] Optional bootstrap panel context class (danger, warning, info, success) * @param {executeForElement} executeOnEachPanel */ - function wholePanelAll(tabId, category, executeOnEachPanel) { + function wholePanelAll(category, executeOnEachPanel) { var selector = "div.panel"; if (typeof category === "string" && category) { selector += "-" + category; } - var jqPanels = $(selector, $("#" + tabId)); + var jqPanels = $(selector); /** @type {Element[]} */ var elemArray = jqPanels.toArray(); @@ -87,17 +86,16 @@ } /** - * @param {string} tabId The id of the tabpanel div to search. * @param {string} [category] Optional bootstrap panel context class (danger, warning, info, success) * @param {executeForElement} executeOnEachPanel */ - function panelBodyAll(tabId, category, executeOnEachPanelBody) { + function panelBodyAll(category, executeOnEachPanelBody) { var selector = "div.panel"; if (typeof category === "string" && category) { selector += "-" + category; } - var jqPanels = $(selector, $("#" + tabId)); + var jqPanels = $(selector); var jqPanelBodies = $("div.panel-body", jqPanels); /** @type {Element[]} */ @@ -107,35 +105,31 @@ } /** - * @param {string} tabId The id of the tabpanel div to search. * @param {string} [category] Optional bootstrap panel context class (danger, warning, info, success) */ - function showAll(tabId, category) { - wholePanelAll(tabId, category, executeShowOnElement); + function showAll(category) { + wholePanelAll(category, executeShowOnElement); } /** - * @param {string} tabId The id of the tabpanel div to search. * @param {string} [category] Optional bootstrap panel context class (danger, warning, info, success) */ - function hideAll(tabId, category) { - wholePanelAll(tabId, category, executeHideOnElement); + function hideAll(category) { + wholePanelAll(category, executeHideOnElement); } /** - * @param {string} tabId The id of the tabpanel div to search. * @param {string} [category] Optional bootstrap panel context class (danger, warning, info, success) */ - function expandAll(tabId, category) { - panelBodyAll(tabId, category, executeExpandOnElement); + function expandAll(category) { + panelBodyAll(category, executeExpandOnElement); } /** - * @param {string} tabId The id of the tabpanel div to search. * @param {string} [category] Optional bootstrap panel context class (danger, warning, info, success) */ - function collapseAll(tabId, category) { - panelBodyAll(tabId, category, executeCollapseOnElement); + function collapseAll(category) { + panelBodyAll(category, executeCollapseOnElement); } @@ -143,176 +137,161 @@

Testament Test Results Nim Tester

#end proc -#proc generateHtmlTabListBegin*(outfile: File) = - -#end proc -#proc generateHtmlTabContentsBegin*(outfile: File) = -
-#end proc -#proc generateHtmlTabPageBegin*(outfile: File, firstTabActiveClass, commitId, -# machineId, branch, hash, machineName, os, cpu: string, totalCount: BiggestInt, +#proc generateHtmlAllTestsBegin*(outfile: File, machine, commit, branch: string, +# totalCount: BiggestInt, # successCount: BiggestInt, successPercentage: string, # ignoredCount: BiggestInt, ignoredPercentage: string, -# failedCount: BiggestInt, failedPercentage: string) = -
-

%branch#%hash@%machineName

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

No output details

-#end proc -#proc generateHtmlTestresultPanelEnd*(outfile: File) = -
+
+ + + + + + + + + + + + + +
ExpectedActual
%expected
%gotten
#end proc -#proc generateHtmlTabPageEnd*(outfile: File) = +#proc generateHtmlTestresultOutputNone*(outfile: File) = +

No output details

+#end proc +#proc generateHtmlTestresultPanelEnd*(outfile: File) =
#end proc -#proc generateHtmlTabContentsEnd*(outfile: File) = +#proc generateHtmlAllTestsEnd*(outfile: File) =
#end proc -#proc generateHtmlEnd*(outfile: File) = +#proc generateHtmlEnd*(outfile: File, timestamp: string) = +
+
- \ No newline at end of file + diff --git a/tests/testament/tester.nim b/tests/testament/tester.nim index 2f0485135e..918de881ff 100644 --- a/tests/testament/tester.nim +++ b/tests/testament/tester.nim @@ -12,7 +12,7 @@ import parseutils, strutils, pegs, os, osproc, streams, parsecfg, json, marshal, backend, parseopt, specs, htmlgen, browsers, terminal, - algorithm, compiler/nodejs, times, sets + algorithm, compiler/nodejs, times, sets, md5 const resultsFile = "testresults.html" @@ -71,8 +71,15 @@ proc getFileDir(filename: string): string = if not result.isAbsolute(): result = getCurrentDir() / result +proc nimcacheDir(filename, options: string, target: TTarget): string = + ## Give each test a private nimcache dir so they don't clobber each other's. + let hashInput = options & $target + return "nimcache" / (filename & '_' & hashInput.getMD5) + proc callCompiler(cmdTemplate, filename, options: string, - target: TTarget): TSpec = + target: TTarget, extraOptions=""): TSpec = + let nimcache = nimcacheDir(filename, options, target) + let options = options & " --nimCache:" & nimcache.quoteShell & extraOptions let c = parseCmdLine(cmdTemplate % ["target", targetToCmd[target], "options", options, "file", filename.quoteShell, "filedir", filename.getFileDir()]) @@ -158,6 +165,7 @@ proc addResult(r: var TResults, test: TTest, target: TTarget, expected, given: string, success: TResultEnum) = let name = test.name.extractFilename & " " & $target & test.options let duration = epochTime() - test.startTime + let durationStr = duration.formatFloat(ffDecimal, precision = 8) backend.writeTestResult(name = name, category = test.cat.string, target = $target, @@ -167,7 +175,7 @@ proc addResult(r: var TResults, test: TTest, target: TTarget, given = given) r.data.addf("$#\t$#\t$#\t$#", name, expected, given, $success) if success == reSuccess: - styledEcho fgGreen, "PASS: ", fgCyan, name + styledEcho fgGreen, "PASS: ", fgCyan, alignLeft(name, 60), fgBlue, " (", durationStr, " secs)" elif success == reIgnored: styledEcho styleDim, fgYellow, "SKIP: ", styleBright, fgCyan, name else: @@ -221,9 +229,10 @@ proc cmpMsgs(r: var TResults, expected, given: TSpec, test: TTest, target: TTarg r.addResult(test, target, expected.msg, given.msg, reSuccess) inc(r.passed) -proc generatedFile(path, name: string, target: TTarget): string = +proc generatedFile(test: TTest, target: TTarget): string = + let (_, name, _) = test.name.splitFile let ext = targetToExt[target] - result = path / "nimcache" / + result = nimcacheDir(test.name, test.options, target) / (if target == targetJS: "" else: "compiler_") & name.changeFileExt(ext) @@ -233,8 +242,7 @@ proc needsCodegenCheck(spec: TSpec): bool = proc codegenCheck(test: TTest, target: TTarget, spec: TSpec, expectedMsg: var string, given: var TSpec) = try: - let (path, name, _) = test.name.splitFile - let genFile = generatedFile(path, name, target) + let genFile = generatedFile(test, target) let contents = readFile(genFile).string let check = spec.ccodeCheck if check.len > 0: @@ -283,20 +291,6 @@ proc compilerOutputTests(test: TTest, target: TTarget, given: var TSpec, if given.err == reSuccess: inc(r.passed) r.addResult(test, target, expectedmsg, givenmsg, given.err) -proc analyzeAndConsolidateOutput(s: string): string = - result = "" - let rows = s.splitLines - for i in 0 ..< rows.len: - if (let pos = find(rows[i], "Traceback (most recent call last)"); pos != -1): - result = substr(rows[i], pos) & "\n" - for i in i+1 ..< rows.len: - result.add rows[i] & "\n" - if not (rows[i] =~ peg"['(']+ '(' \d+ ')' \s+"): - return - elif (let pos = find(rows[i], "SIGSEGV: Illegal storage access."); pos != -1): - result = substr(rows[i], pos) - return - proc testSpec(r: var TResults, test: TTest, target = targetC) = let tname = test.name.addFileExt(".nim") #echo "TESTING ", tname @@ -324,9 +318,8 @@ proc testSpec(r: var TResults, test: TTest, target = targetC) = case expected.action of actionCompile: - var given = callCompiler(expected.cmd, test.name, - test.options & " --stdout --hint[Path]:off --hint[Processing]:off", - target) + var given = callCompiler(expected.cmd, test.name, test.options, target, + extraOptions=" --stdout --hint[Path]:off --hint[Processing]:off") compilerOutputTests(test, target, given, expected, r) of actionRun, actionRunNoSpec: # In this branch of code "early return" pattern is clearer than deep @@ -341,8 +334,8 @@ proc testSpec(r: var TResults, test: TTest, target = targetC) = let isJsTarget = target == targetJS var exeFile: string if isJsTarget: - let (dir, file, _) = splitFile(tname) - exeFile = dir / "nimcache" / file & ".js" # *TODO* hardcoded "nimcache" + let (_, file, _) = splitFile(tname) + exeFile = nimcacheDir(test.name, test.options, target) / file & ".js" else: exeFile = changeFileExt(tname, ExeExt) @@ -370,8 +363,7 @@ proc testSpec(r: var TResults, test: TTest, target = targetC) = if exitCode != expected.exitCode: r.addResult(test, target, "exitcode: " & $expected.exitCode, "exitcode: " & $exitCode & "\n\nOutput:\n" & - analyzeAndConsolidateOutput(bufB), - reExitCodesDiffer) + bufB, reExitCodesDiffer) continue if bufB != expectedOut and expected.action != actionRunNoSpec: diff --git a/tests/tuples/tuple_with_nil.nim b/tests/tuples/tuple_with_nil.nim index 7f5a359f5b..eb265f420b 100644 --- a/tests/tuples/tuple_with_nil.nim +++ b/tests/tuples/tuple_with_nil.nim @@ -485,7 +485,7 @@ proc writeformat(o: var Writer; b: bool; fmt: Format) = else: raise newException(FormatError, "Boolean values must of one of the following types: s,b,o,x,X,d,n") -proc writeformat(o: var Writer; ary: openarray[any]; fmt: Format) = +proc writeformat(o: var Writer; ary: openarray[system.any]; fmt: Format) = ## Write array `ary` according to format `fmt` using output object ## `o` and output function `add`. if ary.len == 0: return diff --git a/tests/typerel/typeof_in_template.nim b/tests/typerel/typeof_in_template.nim index 9ec06f2e31..3724cc9943 100644 --- a/tests/typerel/typeof_in_template.nim +++ b/tests/typerel/typeof_in_template.nim @@ -1,5 +1,5 @@ discard """ - output: '''@[a, c]''' + output: '''@["a", "c"]''' """ # bug #3230 diff --git a/tests/types/tinheritpartialgeneric.nim b/tests/types/tinheritpartialgeneric.nim index a00df26fad..1845778bf3 100644 --- a/tests/types/tinheritpartialgeneric.nim +++ b/tests/types/tinheritpartialgeneric.nim @@ -1,6 +1,6 @@ discard """ - output: '''(c: hello, a: 10, b: 12.0) -(a: 15.5, b: hello) + output: '''(c: "hello", a: 10, b: 12.0) +(a: 15.5, b: "hello") (a: 11.75, b: 123)''' """ diff --git a/tests/types/tparameterizedparent2.nim b/tests/types/tparameterizedparent2.nim index 999db2ac58..e96b9edbeb 100644 --- a/tests/types/tparameterizedparent2.nim +++ b/tests/types/tparameterizedparent2.nim @@ -2,7 +2,7 @@ discard """ output: '''(width: 11, color: 13) (width: 15, weight: 13, taste: 11, color: 14) (width: 17, color: 16) -(width: 12.0, taste: yummy, color: 13) +(width: 12.0, taste: "yummy", color: 13) (width: 0, tast_e: 0.0, kind: Smooth, skin: 1.5, color: 12)''' """ # bug #5264 diff --git a/tests/untestable/tssl.nim b/tests/untestable/tssl.nim new file mode 100644 index 0000000000..664ad805cf --- /dev/null +++ b/tests/untestable/tssl.nim @@ -0,0 +1,36 @@ +# +# Nim - SSL integration tests +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# +## Warning: this test performs external networking. +## +## Test with: +## ./bin/nim c -d:ssl -p:. -r tests/untestable/tssl.nim +## ./bin/nim c -d:ssl -p:. --dynlibOverride:ssl --passL:-lcrypto --passL:-lssl -r tests/untestable/tssl.nim +## The compilation is expected to succeed with any new/old version of OpenSSL, +## both with dynamic and static linking. +## The "howsmyssl" test is known to fail with OpenSSL < 1.1 due to insecure +## cypher suites being used. + +import httpclient, os +from strutils import contains, toHex + +from openssl import getOpenSSLVersion + +when isMainModule: + echo "version: 0x" & $getOpenSSLVersion().toHex() + + let client = newHttpClient() + # hacky SSL check + const url = "https://www.howsmyssl.com" + let report = client.getContent(url) + if not report.contains(">Probably Okay"): + let fn = getTempDir() / "sslreport.html" + echo "SSL CHECK ERROR, see " & fn + writeFile(fn, report) + quit(1) + + echo "done" diff --git a/tests/vm/tableinstatic.nim b/tests/vm/tableinstatic.nim index 54e7c11f0e..b0d24b4771 100644 --- a/tests/vm/tableinstatic.nim +++ b/tests/vm/tableinstatic.nim @@ -2,7 +2,7 @@ discard """ nimout: '''0 0 0 -{hallo: 123, welt: 456}''' +{"hallo": "123", "welt": "456"}''' """ import tables diff --git a/tests/vm/tcastint.nim b/tests/vm/tcastint.nim new file mode 100644 index 0000000000..7b9ddd7d9f --- /dev/null +++ b/tests/vm/tcastint.nim @@ -0,0 +1,120 @@ +discard """ + file: "tcastint.nim" + output: "OK" +""" + +type + Dollar = distinct int + XCoord = distinct int32 + Digit = range[-9..0] + +# those are necessary for comparisons below. +proc `==`(x, y: Dollar): bool {.borrow.} +proc `==`(x, y: XCoord): bool {.borrow.} + +proc dummy[T](x: T): T = x + +proc test() = + let U8 = 0b1011_0010'u8 + let I8 = 0b1011_0010'i8 + let C8 = 0b1011_0010'u8.char + let C8_1 = 0b1011_0011'u8.char + let U16 = 0b10100111_00101000'u16 + let I16 = 0b10100111_00101000'i16 + let U32 = 0b11010101_10011100_11011010_01010000'u32 + let I32 = 0b11010101_10011100_11011010_01010000'i32 + let U64A = 0b11000100_00111111_01111100_10001010_10011001_01001000_01111010_00010001'u64 + let I64A = 0b11000100_00111111_01111100_10001010_10011001_01001000_01111010_00010001'i64 + let U64B = 0b00110010_11011101_10001111_00101000_00000000_00000000_00000000_00000000'u64 + let I64B = 0b00110010_11011101_10001111_00101000_00000000_00000000_00000000_00000000'i64 + when sizeof(int) == 8: + let UX = U64A.uint + let IX = I64A.int + elif sizeof(int) == 4: + let UX = U32.uint + let IX = I32.int + elif sizeof(int) == 2: + let UX = U16.uint + let IX = I16.int + else: + let UX = U8.uint + let IX = I8.int + + doAssert(cast[char](I8) == C8) + doAssert(cast[uint8](I8) == U8) + doAssert(cast[uint16](I16) == U16) + doAssert(cast[uint32](I32) == U32) + doAssert(cast[uint64](I64A) == U64A) + doAssert(cast[uint64](I64B) == U64B) + doAssert(cast[int8](U8) == I8) + doAssert(cast[int16](U16) == I16) + doAssert(cast[int32](U32) == I32) + doAssert(cast[int64](U64A) == I64A) + doAssert(cast[int64](U64B) == I64B) + doAssert(cast[uint](IX) == UX) + doAssert(cast[int](UX) == IX) + + doAssert(cast[char](I8 + 1) == C8_1) + doAssert(cast[uint8](I8 + 1) == U8 + 1) + doAssert(cast[uint16](I16 + 1) == U16 + 1) + doAssert(cast[uint32](I32 + 1) == U32 + 1) + doAssert(cast[uint64](I64A + 1) == U64A + 1) + doAssert(cast[uint64](I64B + 1) == U64B + 1) + doAssert(cast[int8](U8 + 1) == I8 + 1) + doAssert(cast[int16](U16 + 1) == I16 + 1) + doAssert(cast[int32](U32 + 1) == I32 + 1) + doAssert(cast[int64](U64A + 1) == I64A + 1) + doAssert(cast[int64](U64B + 1) == I64B + 1) + doAssert(cast[uint](IX + 1) == UX + 1) + doAssert(cast[int](UX + 1) == IX + 1) + + doAssert(cast[char](I8.dummy) == C8.dummy) + doAssert(cast[uint8](I8.dummy) == U8.dummy) + doAssert(cast[uint16](I16.dummy) == U16.dummy) + doAssert(cast[uint32](I32.dummy) == U32.dummy) + doAssert(cast[uint64](I64A.dummy) == U64A.dummy) + doAssert(cast[uint64](I64B.dummy) == U64B.dummy) + doAssert(cast[int8](U8.dummy) == I8.dummy) + doAssert(cast[int16](U16.dummy) == I16.dummy) + doAssert(cast[int32](U32.dummy) == I32.dummy) + doAssert(cast[int64](U64A.dummy) == I64A.dummy) + doAssert(cast[int64](U64B.dummy) == I64B.dummy) + doAssert(cast[uint](IX.dummy) == UX.dummy) + doAssert(cast[int](UX.dummy) == IX.dummy) + + + doAssert(cast[int64](if false: U64B else: 0'u64) == (if false: I64B else: 0'i64)) + + block: + let raw = 3 + let money = Dollar(raw) # this must be a variable, is otherwise constant folded. + doAssert(cast[int](money) == raw) + doAssert(cast[Dollar](raw) == money) + block: + let raw = 150'i32 + let position = XCoord(raw) # this must be a variable, is otherwise constant folded. + doAssert(cast[int32](position) == raw) + doAssert(cast[XCoord](raw) == position) + block: + let raw = -2 + let digit = Digit(raw) + doAssert(cast[int](digit) == raw) + doAssert(cast[Digit](raw) == digit) + + when defined nimvm: + doAssert(not compiles(cast[float](I64A))) + doAssert(not compiles(cast[float32](I64A))) + + doAssert(not compiles(cast[char](I64A))) + doAssert(not compiles(cast[uint16](I64A))) + doAssert(not compiles(cast[uint32](I64A))) + + doAssert(not compiles(cast[uint16](I8))) + doAssert(not compiles(cast[uint32](I8))) + doAssert(not compiles(cast[uint64](I8))) + +test() +static: + test() + +echo "OK" diff --git a/tests/vm/tconstobj.nim b/tests/vm/tconstobj.nim index 51f30fb78f..38fcdd844f 100644 --- a/tests/vm/tconstobj.nim +++ b/tests/vm/tconstobj.nim @@ -1,5 +1,5 @@ discard """ - output: '''(name: hello) + output: '''(name: "hello") (-1, 0)''' """ diff --git a/todo.txt b/todo.txt index 1fae1ddacb..e06ddf5554 100644 --- a/todo.txt +++ b/todo.txt @@ -1,8 +1,6 @@ version 1.0 battle plan ======================= -- introduce ``nkStmtListExpr`` for template/macro invokations to produce - better stack traces - let 'doAssert' analyse the expressions and produce more helpful output - fix "high priority" bugs - try to fix as many compiler crashes as reasonable @@ -11,6 +9,8 @@ version 1.0 battle plan Not critical for 1.0 ==================== +- introduce ``nkStmtListExpr`` for template/macro invokations to produce + better stack traces - make 'break' not leave named blocks - make FlowVar compatible to Futures - make 'not nil' the default (produce warnings instead of errors for diff --git a/tools/nimgrep.nim b/tools/nimgrep.nim index 8dff722ec0..e9c1b26fac 100644 --- a/tools/nimgrep.nim +++ b/tools/nimgrep.nim @@ -45,6 +45,9 @@ type TOptions = set[TOption] TConfirmEnum = enum ceAbort, ceYes, ceAll, ceNo, ceNone + Pattern = Regex | Peg + +using pattern: Pattern var filenames: seq[string] = @[] @@ -118,7 +121,7 @@ proc highlight(s, match, repl: string, t: tuple[first, last: int], stdout.write("\n") stdout.flushFile() -proc processFile(filename: string) = +proc processFile(pattern; filename: string) = var filenameShown = false template beforeHighlight = if not filenameShown and optVerbose notin options: @@ -135,18 +138,8 @@ proc processFile(filename: string) = if optVerbose in options: stdout.writeLine(filename) stdout.flushFile() - var pegp: Peg - var rep: Regex var result: string - if optRegex in options: - if {optIgnoreCase, optIgnoreStyle} * options != {}: - rep = re(pattern, {reExtended, reIgnoreCase}) - else: - rep = re(pattern) - else: - pegp = peg(pattern) - if optReplace in options: result = newStringOfCap(buffer.len) @@ -156,11 +149,7 @@ proc processFile(filename: string) = for j in 0..high(matches): matches[j] = "" var reallyReplace = true while i < buffer.len: - var t: tuple[first, last: int] - if optRegex notin options: - t = findBounds(buffer, pegp, matches, i) - else: - t = findBounds(buffer, rep, matches, i) + let t = findBounds(buffer, pattern, matches, i) if t.first < 0: break inc(line, countLines(buffer, i, t.first-1)) @@ -170,11 +159,7 @@ proc processFile(filename: string) = if optReplace notin options: highlight(buffer, wholeMatch, "", t, line, showRepl=false) else: - var r: string - if optRegex notin options: - r = replace(wholeMatch, pegp, replacement % matches) - else: - r = replace(wholeMatch, rep, replacement % matches) + let r = replace(wholeMatch, pattern, replacement % matches) if optConfirm in options: highlight(buffer, wholeMatch, r, t, line, showRepl=true) case confirm() @@ -246,17 +231,17 @@ proc styleInsensitive(s: string): string = addx() else: addx() -proc walker(dir: string) = +proc walker(pattern; dir: string) = for kind, path in walkDir(dir): case kind of pcFile: if extensions.len == 0 or path.hasRightExt(extensions): - processFile(path) + processFile(pattern, path) of pcDir: if optRecursive in options: - walker(path) + walker(pattern, path) else: discard - if existsFile(dir): processFile(dir) + if existsFile(dir): processFile(pattern, dir) proc writeHelp() = stdout.write(Usage) @@ -332,11 +317,18 @@ else: pattern = "\\y " & pattern elif optIgnoreCase in options: pattern = "\\i " & pattern + let pegp = peg(pattern) + for f in items(filenames): + walker(pegp, f) else: + var reflags = {reStudy, reExtended} if optIgnoreStyle in options: pattern = styleInsensitive(pattern) if optWord in options: pattern = r"\b (:?" & pattern & r") \b" - for f in items(filenames): - walker(f) + if {optIgnoreCase, optIgnoreStyle} * options != {}: + reflags.incl reIgnoreCase + let rep = re(pattern, reflags) + for f in items(filenames): + walker(rep, f) diff --git a/tools/niminst/niminst.nim b/tools/niminst/niminst.nim index e4568dc3ab..9c15326b0a 100644 --- a/tools/niminst/niminst.nim +++ b/tools/niminst/niminst.nim @@ -283,7 +283,7 @@ proc yesno(p: var CfgParser, v: string): bool = else: quit(errorStr(p, "unknown value; use: yes|no")) proc incl(s: var seq[string], x: string): int = - for i in 0..