summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md2
-rw-r--r--compiler/ast.nim4
-rw-r--r--compiler/ccgexprs.nim39
-rw-r--r--compiler/ccgstmts.nim6
-rw-r--r--compiler/cgen.nim42
-rw-r--r--compiler/evaltempl.nim10
-rw-r--r--compiler/extccomp.nim56
-rw-r--r--compiler/jsgen.nim2
-rw-r--r--compiler/lambdalifting.nim2
-rw-r--r--compiler/nimblecmd.nim24
-rw-r--r--compiler/renderer.nim4
-rw-r--r--compiler/scriptconfig.nim3
-rw-r--r--compiler/sem.nim2
-rw-r--r--compiler/semexprs.nim14
-rw-r--r--compiler/semfold.nim3
-rw-r--r--compiler/semgnrc.nim2
-rw-r--r--compiler/semstmts.nim3
-rw-r--r--compiler/semtempl.nim4
-rw-r--r--compiler/transf.nim2
-rw-r--r--compiler/vmgen.nim2
-rw-r--r--doc/lib.rst3
-rw-r--r--lib/core/macros.nim2
-rw-r--r--lib/pure/math.nim2
-rw-r--r--lib/pure/os.nim30
-rw-r--r--lib/pure/securehash.nim (renamed from compiler/securehash.nim)0
-rw-r--r--lib/pure/strformat.nim7
-rw-r--r--lib/pure/times.nim15
-rw-r--r--lib/system/excpt.nim12
-rw-r--r--tests/concepts/t6462.nim23
-rw-r--r--tests/concepts/tcomparable.nim13
-rw-r--r--tests/concepts/titerable.nim20
-rw-r--r--tests/testament/htmlgen.nim146
-rw-r--r--tests/testament/specs.nim4
-rw-r--r--tests/testament/testamenthtml.templ327
-rw-r--r--tools/niminst/niminst.nim2
-rw-r--r--web/website.ini2
36 files changed, 468 insertions, 366 deletions
diff --git a/changelog.md b/changelog.md
index d519ecfcf..4d205faf8 100644
--- a/changelog.md
+++ b/changelog.md
@@ -127,8 +127,6 @@ This now needs to be written as:
 - 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.
-- Removed ``securehash`` stdlib module as it is not secure anymore. The module
-  is still available via ``compiler/securehash``.
 - 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
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 5b923acb2..27a44c6c2 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -62,8 +62,8 @@ type
     nkTripleStrLit,       # a triple string literal """
     nkNilLit,             # the nil literal
                           # end of atoms
-    nkMetaNode_Obsolete,  # difficult to explain; represents itself
-                          # (used for macros)
+    nkComesFrom,          # "comes from" template/macro information for
+                          # better stack trace generation
     nkDotCall,            # used to temporarily flag a nkCall node;
                           # this is used
                           # for transforming ``s.len`` to ``len(s)``
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index 1505052cc..5a25a9853 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -683,9 +683,10 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc; enforceDeref=false) =
       d.storage = OnHeap
   else:
     var a: TLoc
-    var typ = skipTypes(e.sons[0].typ, abstractInst)
+    var typ = e.sons[0].typ
     if typ.kind in {tyUserTypeClass, tyUserTypeClassInst} and typ.isResolvedUserTypeClass:
       typ = typ.lastSon
+    typ = typ.skipTypes(abstractInst)
     if typ.kind == tyVar and tfVarIsPtr notin typ.flags and p.module.compileToCpp and e.sons[0].kind == nkHiddenAddr:
       initLocExprSingleUse(p, e[0][0], d)
       return
@@ -849,7 +850,7 @@ proc genArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) =
   var a, b: TLoc
   initLocExpr(p, x, a)
   initLocExpr(p, y, b)
-  var ty = skipTypes(skipTypes(a.t, abstractVarRange), abstractPtrs)
+  var ty = skipTypes(a.t, abstractVarRange + abstractPtrs + tyUserTypeClasses)
   var first = intLiteral(firstOrd(ty))
   # emit range check:
   if optBoundsCheck in p.options and tfUncheckedArray notin ty.flags:
@@ -1982,10 +1983,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
@@ -2184,8 +2210,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.
diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim
index 24a376dec..36816cc2c 100644
--- a/compiler/ccgstmts.nim
+++ b/compiler/ccgstmts.nim
@@ -564,9 +564,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 +577,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/cgen.nim b/compiler/cgen.nim
index 07c2824d0..217138dd0 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:
diff --git a/compiler/evaltempl.nim b/compiler/evaltempl.nim
index 7fa6df3da..704ff819c 100644
--- a/compiler/evaltempl.nim
+++ b/compiler/evaltempl.nim
@@ -109,7 +109,7 @@ proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode =
 var evalTemplateCounter* = 0
   # to prevent endless recursion in templates instantiation
 
-proc wrapInComesFrom*(info: TLineInfo; res: PNode): PNode =
+proc wrapInComesFrom*(info: TLineInfo; sym: PSym; res: PNode): PNode =
   when true:
     result = res
     result.info = info
@@ -124,8 +124,12 @@ proc wrapInComesFrom*(info: TLineInfo; res: PNode): PNode =
           if x[i].kind in nkCallKinds:
             x.sons[i].info = info
   else:
-    result = newNodeI(nkPar, info)
+    result = newNodeI(nkStmtListExpr, info)
+    var d = newNodeI(nkComesFrom, info)
+    d.add newSymNode(sym, info)
+    result.add d
     result.add res
+    result.typ = res.typ
 
 proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym; fromHlo=false): PNode =
   inc(evalTemplateCounter)
@@ -156,6 +160,6 @@ proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym; fromHlo=false): PNode =
     for i in countup(0, safeLen(body) - 1):
       evalTemplateAux(body.sons[i], args, ctx, result)
   result.flags.incl nfFromTemplate
-  result = wrapInComesFrom(n.info, result)
+  result = wrapInComesFrom(n.info, tmpl, result)
   dec(evalTemplateCounter)
 
diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim
index 7a473ea43..5299b2dbf 100644
--- a/compiler/extccomp.nim
+++ b/compiler/extccomp.nim
@@ -794,42 +794,40 @@ proc writeJsonBuildInstructions*(projectfile: string) =
     else:
       f.write escapeJson(x)
 
-  proc cfiles(f: File; buf: var string; list: CfileList, isExternal: bool) =
-    var i = 0
-    for it in list:
+  proc cfiles(f: File; buf: var string; clist: CfileList, isExternal: bool) =
+    var pastStart = false
+    for it in clist:
       if CfileFlag.Cached in it.flags: continue
       let compileCmd = getCompileCFileCmd(it)
+      if pastStart: lit "],\L"
       lit "["
       str it.cname
       lit ", "
       str compileCmd
-      inc i
-      if i == list.len:
-        lit "]\L"
-      else:
-        lit "],\L"
-
-  proc linkfiles(f: File; buf, objfiles: var string) =
-    for i, it in externalToLink:
-      let
-        objFile = if noAbsolutePaths(): it.extractFilename else: it
-        objStr = addFileExt(objFile, CC[cCompiler].objExt)
+      pastStart = true
+    lit "]\L"
+
+  proc linkfiles(f: File; buf, objfiles: var string; clist: CfileList;
+                 llist: seq[string]) =
+    var pastStart = false
+    for it in llist:
+      let objfile = if noAbsolutePaths(): it.extractFilename
+                    else: it
+      let objstr = addFileExt(objfile, CC[cCompiler].objExt)
       add(objfiles, ' ')
-      add(objfiles, objStr)
-      str objStr
-      if toCompile.len == 0 and i == externalToLink.high:
-        lit "\L"
-      else:
-        lit ",\L"
-    for i, x in toCompile:
-      let objStr = quoteShell(x.obj)
+      add(objfiles, objstr)
+      if pastStart: lit ",\L"
+      str objstr
+      pastStart = true
+
+    for it in clist:
+      let objstr = quoteShell(it.obj)
       add(objfiles, ' ')
-      add(objfiles, objStr)
-      str objStr
-      if i == toCompile.high:
-        lit "\L"
-      else:
-        lit ",\L"
+      add(objfiles, objstr)
+      if pastStart: lit ",\L"
+      str objstr
+      pastStart = true
+    lit "\L"
 
   var buf = newStringOfCap(50)
 
@@ -843,7 +841,7 @@ proc writeJsonBuildInstructions*(projectfile: string) =
     lit "],\L\"link\":[\L"
     var objfiles = ""
     # XXX add every file here that is to link
-    linkfiles(f, buf, objfiles)
+    linkfiles(f, buf, objfiles, toCompile, externalToLink)
 
     lit "],\L\"linkcmd\": "
     str getLinkCmd(projectfile, objfiles)
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index 50dfa22f8..65a6a5dae 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -2369,6 +2369,8 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
   of nkGotoState, nkState:
     internalError(n.info, "first class iterators not implemented")
   of nkPragmaBlock: gen(p, n.lastSon, r)
+  of nkComesFrom:
+    discard "XXX to implement for better stack traces"
   else: internalError(n.info, "gen: unknown node type: " & $n.kind)
 
 var globals: PGlobals
diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim
index 820439524..cf43ba15d 100644
--- a/compiler/lambdalifting.nim
+++ b/compiler/lambdalifting.nim
@@ -723,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:
diff --git a/compiler/nimblecmd.nim b/compiler/nimblecmd.nim
index 39c3a17e7..0f9e03352 100644
--- a/compiler/nimblecmd.nim
+++ b/compiler/nimblecmd.nim
@@ -28,6 +28,10 @@ proc newVersion*(ver: string): Version =
 proc isSpecial(ver: Version): bool =
   return ($ver).len > 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..<p.len:
+    if p[i] in {DirSep, AltSep}:
+      result.name = p
+      return
+
   result.name = p[0 .. sepIdx - 1]
   result.version = p.substr(sepIdx + 1)
 
-proc addPackage(packages: StringTableRef, p: string) =
+proc addPackage(packages: StringTableRef, p: string; info: TLineInfo) =
   let (name, ver) = getPathVersion(p)
-  let version = newVersion(ver)
-  if packages.getOrDefault(name).newVersion < version or
-     (not packages.hasKey(name)):
-    packages[name] = $version
+  if isValidVersion(ver):
+    let version = newVersion(ver)
+    if packages.getOrDefault(name).newVersion < version or
+      (not packages.hasKey(name)):
+      packages[name] = $version
+  else:
+    localError(info, "invalid package name: " & p)
 
 iterator chosen(packages: StringTableRef): string =
   for key, val in pairs(packages):
@@ -109,7 +121,7 @@ proc addPathRec(dir: string, info: TLineInfo) =
   if dir[pos] in {DirSep, AltSep}: inc(pos)
   for k,p in os.walkDir(dir):
     if k == pcDir and p[pos] != '.':
-      addPackage(packages, p)
+      addPackage(packages, p, info)
   for p in packages.chosen:
     addNimblePath(p, info)
 
diff --git a/compiler/renderer.nim b/compiler/renderer.nim
index 267ce7de7..d4b401c02 100644
--- a/compiler/renderer.nim
+++ b/compiler/renderer.nim
@@ -1406,8 +1406,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/scriptconfig.nim b/compiler/scriptconfig.nim
index dac267263..8eb76457c 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 495321de4..bc994201d 100644
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -423,7 +423,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 =
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index 7867c7e36..55c43ed09 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -1365,13 +1365,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:
@@ -2380,6 +2383,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 1e7c0aa9e..d2d36140d 100644
--- a/compiler/semfold.nim
+++ b/compiler/semfold.nim
@@ -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 da2c6fe7f..16da06952 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/semstmts.nim b/compiler/semstmts.nim
index c85de35cd..8ed120c98 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -441,9 +441,10 @@ 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})
   result = n
   if t.kind == tyVar:
     result = newNodeIT(nkHiddenDeref, n.info, t.sons[0])
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
index 1c9d8271a..f90dff8f1 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',
diff --git a/compiler/transf.nim b/compiler/transf.nim
index 8e4bb935b..6bc809fd2 100644
--- a/compiler/transf.nim
+++ b/compiler/transf.nim
@@ -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)
diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim
index 252b7c788..3790a8392 100644
--- a/compiler/vmgen.nim
+++ b/compiler/vmgen.nim
@@ -1847,6 +1847,8 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) =
       globalError(n.info, errGenerated, "VM is not allowed to 'cast'")
   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/doc/lib.rst b/doc/lib.rst
index 2719472fe..58dedc49c 100644
--- a/doc/lib.rst
+++ b/doc/lib.rst
@@ -380,6 +380,9 @@ Cryptography and Hashing
 * `base64 <base64.html>`_
   This module implements a base64 encoder and decoder.
 
+* `securehash <securehash.html>`_
+  This module implements a sha1 encoder and decoder.
+
 
 Multimedia support
 ------------------
diff --git a/lib/core/macros.nim b/lib/core/macros.nim
index ee6c1a09f..b08a2198e 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,
diff --git a/lib/pure/math.nim b/lib/pure/math.nim
index 7fd8bbcef..a9dabfa48 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.}
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index 87f6def29..c18d03289 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -816,32 +816,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):
diff --git a/compiler/securehash.nim b/lib/pure/securehash.nim
index 57c1f3631..57c1f3631 100644
--- a/compiler/securehash.nim
+++ b/lib/pure/securehash.nim
diff --git a/lib/pure/strformat.nim b/lib/pure/strformat.nim
index c4044867d..180cbcbec 100644
--- a/lib/pure/strformat.nim
+++ b/lib/pure/strformat.nim
@@ -42,7 +42,7 @@ An expression like ``fmt"{key} is {value:arg} {{z}}"`` is transformed into:
   format(key, temp)
   format(" is ", temp)
   format(value, arg, temp)
-  format("{z}", temp)
+  format(" {z}", temp)
   temp
 
 Parts of the string that are enclosed in the curly braces are interpreted
@@ -94,7 +94,7 @@ The general form of a standard format specifier is::
 
   [[fill]align][sign][#][0][minimumwidth][.precision][type]
 
-The brackets ([]) indicate an optional element.
+The square brackets ``[]`` indicate an optional element.
 
 The optional align flag can be one of the following:
 
@@ -126,8 +126,9 @@ The 'sign' option is only valid for numeric types, and can be one of the followi
                          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
+(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
diff --git a/lib/pure/times.nim b/lib/pure/times.nim
index dcc817b7b..606acbc1c 100644
--- a/lib/pure/times.nim
+++ b/lib/pure/times.nim
@@ -26,7 +26,6 @@
 ##  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      : ", now() + 1.hours
 ##  echo "An hour from (UTC) now: ", getTime().utc + initInterval(0,0,0,1)
@@ -180,7 +179,7 @@ proc assertValidDate(monthday: MonthdayRange, month: Month, year: int) {.inline.
 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  
+  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:
@@ -194,7 +193,7 @@ proc toEpochDay*(monthday: MonthdayRange, month: Month, year: int): int64 =
 
 proc fromEpochDay*(epochday: int64): tuple[monthday: MonthdayRange, month: Month, year: int] =
   ## Get the year/month/day date from a epoch day.
-  ## The epoch day is the number of days since 1970/01/01 (it might be negative).  
+  ## The epoch day is the number of days since 1970/01/01 (it might be negative).
   # Based on http://howardhinnant.github.io/date_algorithms.html
   var z = epochday
   z.inc 719468
@@ -494,11 +493,11 @@ proc local*(dt: DateTime): DateTime =
   dt.inZone(local())
 
 proc utc*(t: Time): DateTime =
-  ## Shorthand for ``t.inZone(utc())``.  
+  ## Shorthand for ``t.inZone(utc())``.
   t.inZone(utc())
 
 proc local*(t: Time): DateTime =
-  ## Shorthand for ``t.inZone(local())``.  
+  ## Shorthand for ``t.inZone(local())``.
   t.inZone(local())
 
 proc getTime*(): Time {.tags: [TimeEffect], benign.}
@@ -590,7 +589,7 @@ proc evaluateInterval(dt: DateTime, interval: TimeInterval): tuple[adjDiff, absD
         anew.year.dec()
       else:
         curMonth.dec()
-      result.adjDiff -= getDaysInMonth(curMonth, anew.year) * secondsInDay      
+      result.adjDiff -= getDaysInMonth(curMonth, anew.year) * secondsInDay
   # Adding
   else:
     for mth in 1 .. newinterv.months:
@@ -610,7 +609,7 @@ proc `+`*(dt: DateTime, interval: TimeInterval): DateTime =
   ## Adds ``interval`` to ``dt``. Components from ``interval`` are added
   ## in the order of their size, i.e first the ``years`` component, then the ``months``
   ## component and so on. The returned ``DateTime`` will have the same timezone as the input.
-  ## 
+  ##
   ## Note that when adding months, monthday overflow is allowed. This means that if the resulting
   ## month doesn't have enough days it, the month will be incremented and the monthday will be
   ## set to the number of days overflowed. So adding one month to `31 October` will result in `31 November`,
@@ -1392,7 +1391,7 @@ proc timeInfoToTime*(dt: DateTime): Time {.tags: [], benign, deprecated.} =
   ##
   ## **Warning:** This procedure is deprecated since version 0.14.0.
   ## Use ``toTime`` instead.
-  dt.toTime  
+  dt.toTime
 
 when defined(JS):
   var startMilsecs = getTime()
diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim
index 70c18ae21..8e42ea468 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/tests/concepts/t6462.nim b/tests/concepts/t6462.nim
new file mode 100644
index 000000000..2fa2268f8
--- /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 000000000..06612a47e
--- /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 000000000..b18658b2a
--- /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/testament/htmlgen.nim b/tests/testament/htmlgen.nim
index 05c24b2b5..bf26a956d 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
-
-  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) =
+        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)
+
+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")
-
-  outfile.generateHtmlTabPageBegin(
-    firstTabActiveClass, commitId,
-    machineId, branch, hash, machineName, os, cpu,
+    firstRow = allResults.data[0]
+    commit = htmlQuote firstRow["commit"].str
+    branch = htmlQuote firstRow["branch"].str
+    machine = htmlQuote firstRow["machine"].str
+
+  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 e8513ab24..ac79e3942 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 f7477f3aa..9190f370e 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);
         }
     </script>
 </head>
@@ -143,176 +137,161 @@
     <div class="container">
         <h1>Testament Test Results <small>Nim Tester</small></h1>
 #end proc
-#proc generateHtmlTabListBegin*(outfile: File) =
-        <ul class="nav nav-tabs" role="tablist">
-#end proc
-#proc generateHtmlTabListItem*(outfile: File, firstTabActiveClass, commitId, 
-#  machineId, branch, hash, machineName: string) =
-            <li role="presentation" class="%firstTabActiveClass">
-                <a href="#tab-commit-%commitId-machine-%machineId" aria-controls="tab-commit-%commitId-machine-%machineId" role="tab" data-toggle="tab">
-                    %branch#%hash@%machineName
-                </a>
-            </li>
-#end proc
-#proc generateHtmlTabListEnd*(outfile: File) =
-        </ul>
-#end proc
-#proc generateHtmlTabContentsBegin*(outfile: File) =
-        <div class="tab-content">
-#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) =
-            <div id="tab-commit-%commitId-machine-%machineId" class="tab-pane fade%firstTabActiveClass" role="tabpanel">
-                <h2>%branch#%hash@%machineName</h2>
-                <dl class="dl-horizontal">
-                    <dt>Branch</dt>
-                    <dd>%branch</dd>
-                    <dt>Commit Hash</dt>
-                    <dd><code>%hash</code></dd>
-                    <dt>Machine Name</dt>
-                    <dd>%machineName</dd>
-                    <dt>OS</dt>
-                    <dd>%os</dd>
-                    <dt title="CPU Architecture">CPU</dt>
-                    <dd>%cpu</dd>
-                    <dt>All Tests</dt>
-                    <dd>
-                        <span class="glyphicon glyphicon-th-list"></span>
-                        %totalCount
-                    </dd>
-                    <dt>Successful Tests</dt>
-                    <dd>
-                        <span class="glyphicon glyphicon-ok-sign"></span>
-                        %successCount (%successPercentage)
-                    </dd>
-                    <dt>Skipped Tests</dt>
-                    <dd>
-                        <span class="glyphicon glyphicon-question-sign"></span>
-                        %ignoredCount (%ignoredPercentage)
-                    </dd>
-                    <dt>Failed Tests</dt>
-                    <dd>
-                        <span class="glyphicon glyphicon-exclamation-sign"></span>
-                        %failedCount (%failedPercentage)
-                    </dd>
-                </dl>
-                <div class="table-responsive">
-                    <table class="table table-condensed">
-                        <tr>
-                            <th class="text-right" style="vertical-align:middle">All Tests</th>
-                            <td>
-                                <div class="btn-group">
-                                    <button class="btn btn-default" type="button" onclick="showAll('tab-commit-%commitId-machine-%machineId');">Show All</button>
-                                    <button class="btn btn-default" type="button" onclick="hideAll('tab-commit-%commitId-machine-%machineId');">Hide All</button>
-                                    <button class="btn btn-default" type="button" onclick="expandAll('tab-commit-%commitId-machine-%machineId');">Expand All</button>
-                                    <button class="btn btn-default" type="button" onclick="collapseAll('tab-commit-%commitId-machine-%machineId');">Collapse All</button>
-                                </div>
-                            </td>
-                        </tr>
-                        <tr>
-                            <th class="text-right" style="vertical-align:middle">Successful Tests</th>
-                            <td>
-                                <div class="btn-group">
-                                    <button class="btn btn-default" type="button" onclick="showAll('tab-commit-%commitId-machine-%machineId', 'success');">Show All</button>
-                                    <button class="btn btn-default" type="button" onclick="hideAll('tab-commit-%commitId-machine-%machineId', 'success');">Hide All</button>
-                                    <button class="btn btn-default" type="button" onclick="expandAll('tab-commit-%commitId-machine-%machineId', 'success');">Expand All</button>
-                                    <button class="btn btn-default" type="button" onclick="collapseAll('tab-commit-%commitId-machine-%machineId', 'success');">Collapse All</button>
-                                </div>
-                            </td>
-                        </tr>
-                        <tr>
-                            <th class="text-right" style="vertical-align:middle">Skipped Tests</th>
-                            <td>
-                                <div class="btn-group">
-                                    <button class="btn btn-default" type="button" onclick="showAll('tab-commit-%commitId-machine-%machineId', 'info');">Show All</button>
-                                    <button class="btn btn-default" type="button" onclick="hideAll('tab-commit-%commitId-machine-%machineId', 'info');">Hide All</button>
-                                    <button class="btn btn-default" type="button" onclick="expandAll('tab-commit-%commitId-machine-%machineId', 'info');">Expand All</button>
-                                    <button class="btn btn-default" type="button" onclick="collapseAll('tab-commit-%commitId-machine-%machineId', 'info');">Collapse All</button>
-                                </div>
-                            </td>
-                        </tr>
-                        <tr>
-                            <th class="text-right" style="vertical-align:middle">Failed Tests</th>
-                            <td>
-                                <div class="btn-group">
-                                    <button class="btn btn-default" type="button" onclick="showAll('tab-commit-%commitId-machine-%machineId', 'danger');">Show All</button>
-                                    <button class="btn btn-default" type="button" onclick="hideAll('tab-commit-%commitId-machine-%machineId', 'danger');">Hide All</button>
-                                    <button class="btn btn-default" type="button" onclick="expandAll('tab-commit-%commitId-machine-%machineId', 'danger');">Expand All</button>
-                                    <button class="btn btn-default" type="button" onclick="collapseAll('tab-commit-%commitId-machine-%machineId', 'danger');">Collapse All</button>
-                                </div>
-                            </td>
-                        </tr>
-                    </table>
-                </div>
-                <div class="panel-group">
+#  failedCount: BiggestInt, failedPercentage: string, onlyFailing = false) =
+        <dl class="dl-horizontal">
+            <dt>Hostname</dt>
+            <dd>%machine</dd>
+            <dt>Git Commit</dt>
+            <dd><code>%commit</code></dd>
+            <dt title="Git Branch reference">Branch ref.</dt>
+            <dd>%branch</dd>
+        </dl>
+        <dl class="dl-horizontal">
+            <dt>All Tests</dt>
+            <dd>
+                <span class="glyphicon glyphicon-th-list"></span>
+                %totalCount
+            </dd>
+            <dt>Successful Tests</dt>
+            <dd>
+                <span class="glyphicon glyphicon-ok-sign"></span>
+                %successCount (%successPercentage)
+            </dd>
+            <dt>Skipped Tests</dt>
+            <dd>
+                <span class="glyphicon glyphicon-question-sign"></span>
+                %ignoredCount (%ignoredPercentage)
+            </dd>
+            <dt>Failed Tests</dt>
+            <dd>
+                <span class="glyphicon glyphicon-exclamation-sign"></span>
+                %failedCount (%failedPercentage)
+            </dd>
+        </dl>
+        <div class="table-responsive">
+            <table class="table table-condensed">
+#  if not onlyFailing:
+                <tr>
+                    <th class="text-right" style="vertical-align:middle">All Tests</th>
+                    <td>
+                        <div class="btn-group">
+                            <button class="btn btn-default" type="button" onclick="showAll();">Show All</button>
+                            <button class="btn btn-default" type="button" onclick="hideAll();">Hide All</button>
+                            <button class="btn btn-default" type="button" onclick="expandAll();">Expand All</button>
+                            <button class="btn btn-default" type="button" onclick="collapseAll();">Collapse All</button>
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <th class="text-right" style="vertical-align:middle">Successful Tests</th>
+                    <td>
+                        <div class="btn-group">
+                            <button class="btn btn-default" type="button" onclick="showAll('success');">Show All</button>
+                            <button class="btn btn-default" type="button" onclick="hideAll('success');">Hide All</button>
+                            <button class="btn btn-default" type="button" onclick="expandAll('success');">Expand All</button>
+                            <button class="btn btn-default" type="button" onclick="collapseAll('success');">Collapse All</button>
+                        </div>
+                    </td>
+                </tr>
+#  end if
+                <tr>
+                    <th class="text-right" style="vertical-align:middle">Skipped Tests</th>
+                    <td>
+                        <div class="btn-group">
+                            <button class="btn btn-default" type="button" onclick="showAll('info');">Show All</button>
+                            <button class="btn btn-default" type="button" onclick="hideAll('info');">Hide All</button>
+                            <button class="btn btn-default" type="button" onclick="expandAll('info');">Expand All</button>
+                            <button class="btn btn-default" type="button" onclick="collapseAll('info');">Collapse All</button>
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <th class="text-right" style="vertical-align:middle">Failed Tests</th>
+                    <td>
+                        <div class="btn-group">
+                            <button class="btn btn-default" type="button" onclick="showAll('danger');">Show All</button>
+                            <button class="btn btn-default" type="button" onclick="hideAll('danger');">Hide All</button>
+                            <button class="btn btn-default" type="button" onclick="expandAll('danger');">Expand All</button>
+                            <button class="btn btn-default" type="button" onclick="collapseAll('danger');">Collapse All</button>
+                        </div>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div class="panel-group">
 #end proc
 #proc generateHtmlTestresultPanelBegin*(outfile: File, trId, name, target, category,
-#  action, resultDescription, timestamp, result, resultSign, 
+#  action, resultDescription, timestamp, result, resultSign,
 #  panelCtxClass, textCtxClass, bgCtxClass: string) =
-                    <div id="panel-testResult-%trId" class="panel panel-%panelCtxClass">
-                        <div class="panel-heading" style="cursor:pointer" data-toggle="collapse" data-target="#panel-body-testResult-%trId" aria-controls="panel-body-testResult-%trId" aria-expanded="false">
-                            <div class="row">
-                                <h4 class="col-xs-3 col-sm-1 panel-title">
-                                    <span class="glyphicon glyphicon-%resultSign-sign"></span>
-                                    <strong>%resultDescription</strong>
-                                </h4>
-                                <h4 class="col-xs-1 panel-title"><span class="badge">%target</span></h4>
-                                <h4 class="col-xs-5 col-sm-7 panel-title" title="%name"><code class="text-%textCtxClass">%name</code></h4>
-                                <h4 class="col-xs-3 col-sm-3 panel-title text-right"><span class="badge">%category</span></h4>
-                            </div>
-                        </div>
-                        <div id="panel-body-testResult-%trId" class="panel-body collapse bg-%bgCtxClass">
-                            <dl class="dl-horizontal">
-                                <dt>Name</dt>
-                                <dd><code class="text-%textCtxClass">%name</code></dd>
-                                <dt>Category</dt>
-                                <dd><span class="badge">%category</span></dd>
-                                <dt>Timestamp</dt>
-                                <dd>%timestamp</dd>
-                                <dt>Nim Action</dt>
-                                <dd><code class="text-%textCtxClass">%action</code></dd>
-                                <dt>Nim Backend Target</dt>
-                                <dd><span class="badge">%target</span></dd>
-                                <dt>Code</dt>
-                                <dd><code class="text-%textCtxClass">%result</code></dd>
-                            </dl>
+            <div id="panel-testResult-%trId" class="panel panel-%panelCtxClass">
+                <div class="panel-heading" style="cursor:pointer" data-toggle="collapse" data-target="#panel-body-testResult-%trId" aria-controls="panel-body-testResult-%trId" aria-expanded="false">
+                    <div class="row">
+                        <h4 class="col-xs-3 col-sm-1 panel-title">
+                            <span class="glyphicon glyphicon-%resultSign-sign"></span>
+                            <strong>%resultDescription</strong>
+                        </h4>
+                        <h4 class="col-xs-1 panel-title"><span class="badge">%target</span></h4>
+                        <h4 class="col-xs-5 col-sm-7 panel-title" title="%name"><code class="text-%textCtxClass">%name</code></h4>
+                        <h4 class="col-xs-3 col-sm-3 panel-title text-right"><span class="badge">%category</span></h4>
+                    </div>
+                </div>
+                <div id="panel-body-testResult-%trId" class="panel-body collapse bg-%bgCtxClass">
+                    <dl class="dl-horizontal">
+                        <dt>Name</dt>
+                        <dd><code class="text-%textCtxClass">%name</code></dd>
+                        <dt>Category</dt>
+                        <dd><span class="badge">%category</span></dd>
+                        <dt>Timestamp</dt>
+                        <dd>%timestamp</dd>
+                        <dt>Nim Action</dt>
+                        <dd><code class="text-%textCtxClass">%action</code></dd>
+                        <dt>Nim Backend Target</dt>
+                        <dd><span class="badge">%target</span></dd>
+                        <dt>Code</dt>
+                        <dd><code class="text-%textCtxClass">%result</code></dd>
+                    </dl>
 #end proc
 #proc generateHtmlTestresultOutputDetails*(outfile: File, expected, gotten: string) =
-                            <div class="table-responsive">
-                                <table class="table table-condensed">
-                                    <thead>
-                                        <tr>
-                                            <th>Expected</th>
-                                            <th>Actual</th>
-                                        </tr>
-                                    </thead>
-                                    <tbody>
-                                        <tr>
-                                            <td><pre>%expected</pre></td>
-                                            <td><pre>%gotten</pre></td>
-                                        </tr>
-                                    </tbody>
-                                </table>
-                            </div>
+                    <div class="table-responsive">
+                        <table class="table table-condensed">
+                            <thead>
+                                <tr>
+                                    <th>Expected</th>
+                                    <th>Actual</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                <tr>
+                                    <td><pre>%expected</pre></td>
+                                    <td><pre>%gotten</pre></td>
+                                </tr>
+                            </tbody>
+                        </table>
+                    </div>
 #end proc
 #proc generateHtmlTestresultOutputNone*(outfile: File) =
-                            <p class="sr-only">No output details</p>
+                    <p class="sr-only">No output details</p>
 #end proc
 #proc generateHtmlTestresultPanelEnd*(outfile: File) =
-                        </div>
-                    </div>
-#end proc
-#proc generateHtmlTabPageEnd*(outfile: File) =
                 </div>
             </div>
 #end proc
-#proc generateHtmlTabContentsEnd*(outfile: File) =
+#proc generateHtmlAllTestsEnd*(outfile: File) =
         </div>
 #end proc
-#proc generateHtmlEnd*(outfile: File) =
+#proc generateHtmlEnd*(outfile: File, timestamp: string) =
+        <hr />
+        <footer>
+            <p>
+                Report generated by: <code>testament</code> &ndash; Nim Tester
+                <br />
+                Made with Nim. Generated on: %timestamp
+            </p>
+        </footer>
     </div>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/tools/niminst/niminst.nim b/tools/niminst/niminst.nim
index ab0ce6a5b..9c15326b0 100644
--- a/tools/niminst/niminst.nim
+++ b/tools/niminst/niminst.nim
@@ -15,7 +15,7 @@ when haveZipLib:
 
 import
   os, osproc, strutils, parseopt, parsecfg, strtabs, streams, debcreation,
-  "../../compiler/securehash"
+  securehash
 
 const
   maxOS = 20 # max number of OSes
diff --git a/web/website.ini b/web/website.ini
index d8deb2d70..32b1936d5 100644
--- a/web/website.ini
+++ b/web/website.ini
@@ -64,7 +64,7 @@ srcdoc2: "pure/asyncfile;pure/asyncftpclient;pure/lenientops"
 srcdoc2: "pure/md5;pure/rationals"
 srcdoc2: "posix/posix;pure/distros;pure/oswalkdir"
 srcdoc2: "pure/collections/heapqueue"
-srcdoc2: "pure/fenv;impure/rdstdin;pure/strformat"
+srcdoc2: "pure/fenv;pure/securehash;impure/rdstdin;pure/strformat"
 srcdoc2: "pure/segfaults"
 srcdoc2: "pure/basic2d;pure/basic3d;pure/mersenne;pure/coro;pure/httpcore"
 srcdoc2: "pure/bitops;pure/nimtracker;pure/punycode;pure/volatile;js/asyncjs"