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