summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--.travis.yml5
-rw-r--r--bootstrap.sh20
-rw-r--r--build_tools.sh3
-rw-r--r--ci/nsis_build.bat11
-rw-r--r--compiler.nimble2
-rw-r--r--compiler/ast.nim20
-rw-r--r--compiler/ccgexprs.nim13
-rw-r--r--compiler/ccgtypes.nim19
-rw-r--r--compiler/ccgutils.nim5
-rw-r--r--compiler/cgen.nim16
-rw-r--r--compiler/cgendata.nim1
-rw-r--r--compiler/cgmeth.nim5
-rw-r--r--compiler/commands.nim36
-rw-r--r--compiler/depends.nim4
-rw-r--r--compiler/docgen.nim8
-rw-r--r--compiler/docgen2.nim4
-rw-r--r--compiler/filter_tmpl.nim23
-rw-r--r--compiler/filters.nim8
-rw-r--r--compiler/idents.nim51
-rw-r--r--compiler/importer.nim24
-rw-r--r--compiler/installer.ini14
-rw-r--r--compiler/jsgen.nim15
-rw-r--r--compiler/lexer.nim94
-rw-r--r--compiler/lookups.nim17
-rw-r--r--compiler/lowerings.nim6
-rw-r--r--compiler/magicsys.nim10
-rw-r--r--compiler/main.nim219
-rw-r--r--compiler/modulegraphs.nim112
-rw-r--r--compiler/modules.nim319
-rw-r--r--compiler/msgs.nim10
-rw-r--r--compiler/nim.nim23
-rw-r--r--compiler/nimconf.nim26
-rw-r--r--compiler/nimeval.nim11
-rw-r--r--compiler/nimfix/nimfix.nim2
-rw-r--r--compiler/nimsuggest/nimsuggest.nim12
-rw-r--r--compiler/nversion.nim2
-rw-r--r--compiler/options.nim7
-rw-r--r--compiler/parampatterns.nim2
-rw-r--r--compiler/parser.nim31
-rw-r--r--compiler/passaux.nim6
-rw-r--r--compiler/passes.nim69
-rw-r--r--compiler/pbraces.nim1772
-rw-r--r--compiler/pragmas.nim3
-rw-r--r--compiler/renderer.nim5
-rw-r--r--compiler/rodread.nim26
-rw-r--r--compiler/rodwrite.nim24
-rw-r--r--compiler/scriptconfig.nim22
-rw-r--r--compiler/sem.nim16
-rw-r--r--compiler/semasgn.nim7
-rw-r--r--compiler/semcall.nim2
-rw-r--r--compiler/semdata.nim54
-rw-r--r--compiler/semdestruct.nim2
-rw-r--r--compiler/semexprs.nim56
-rw-r--r--compiler/semgnrc.nim4
-rw-r--r--compiler/seminst.nim5
-rw-r--r--compiler/semmagic.nim4
-rw-r--r--compiler/sempass2.nim12
-rw-r--r--compiler/semstmts.nim14
-rw-r--r--compiler/service.nim6
-rw-r--r--compiler/sigmatch.nim25
-rw-r--r--compiler/suggest.nim2
-rw-r--r--compiler/syntaxes.nim69
-rw-r--r--compiler/trees.nim2
-rw-r--r--compiler/types.nim59
-rw-r--r--compiler/typesrenderer.nim18
-rw-r--r--compiler/vm.nim59
-rw-r--r--compiler/vmdef.nim8
-rw-r--r--compiler/vmdeps.nim14
-rw-r--r--compiler/vmgen.nim2
-rw-r--r--compiler/wordrecg.nim21
-rw-r--r--config/nim.cfg22
-rw-r--r--contributors.txt12
-rw-r--r--doc/advopt.txt3
-rw-r--r--doc/astspec.txt1
-rw-r--r--doc/basicopt.txt2
-rw-r--r--doc/contributing.rst (renamed from contributing.rst)0
-rw-r--r--doc/docs.rst (renamed from doc/docs.txt)0
-rw-r--r--doc/docstyle.rst (renamed from docstyle.rst)0
-rw-r--r--doc/gc.rst30
-rw-r--r--doc/intern.txt2
-rw-r--r--doc/lib.rst5
-rw-r--r--doc/manual/exceptions.txt2
-rw-r--r--doc/manual/types.txt3
-rw-r--r--doc/nimc.rst4
-rw-r--r--doc/nims.rst18
-rw-r--r--doc/overview.rst (renamed from doc/overview.txt)2
-rw-r--r--icons/koch-amd64-windows-vcc.resbin0 -> 5572 bytes
-rw-r--r--icons/koch-i386-windows-vcc.resbin0 -> 5572 bytes
-rw-r--r--icons/nim-amd64-windows-vcc.resbin0 -> 30548 bytes
-rw-r--r--icons/nim-i386-windows-vcc.resbin0 -> 30548 bytes
-rw-r--r--install.txt12
-rw-r--r--install_nimble.nims4
-rw-r--r--install_tools.nims6
-rw-r--r--koch.nim296
-rw-r--r--lib/arch/arch.nim2
-rw-r--r--lib/core/macros.nim40
-rw-r--r--lib/impure/nre.nim8
-rw-r--r--lib/impure/re.nim9
-rw-r--r--lib/js/jsconsole.nim44
-rw-r--r--lib/nimbase.h4
-rw-r--r--lib/packages/docutils/highlite.nim15
-rw-r--r--lib/posix/kqueue.nim2
-rw-r--r--lib/pure/asyncdispatch.nim57
-rw-r--r--lib/pure/asyncfile.nim17
-rw-r--r--lib/pure/asynchttpserver.nim32
-rw-r--r--lib/pure/collections/deques.nim266
-rw-r--r--lib/pure/collections/queues.nim4
-rw-r--r--lib/pure/collections/sequtils.nim4
-rw-r--r--lib/pure/collections/tableimpl.nim14
-rw-r--r--lib/pure/collections/tables.nim103
-rw-r--r--lib/pure/future.nim21
-rw-r--r--lib/pure/htmlparser.nim11
-rw-r--r--lib/pure/httpclient.nim74
-rw-r--r--lib/pure/includes/asyncfutures.nim34
-rw-r--r--lib/pure/ioselectors.nim8
-rw-r--r--lib/pure/ioselects/ioselectors_kqueue.nim10
-rw-r--r--lib/pure/json.nim44
-rw-r--r--lib/pure/logging.nim18
-rw-r--r--lib/pure/marshal.nim7
-rw-r--r--lib/pure/net.nim4
-rw-r--r--lib/pure/nimtracker.nim80
-rw-r--r--lib/pure/os.nim218
-rw-r--r--lib/pure/ospaths.nim4
-rw-r--r--lib/pure/osproc.nim10
-rw-r--r--lib/pure/parsecfg.nim14
-rw-r--r--lib/pure/parsexml.nim36
-rw-r--r--lib/pure/random.nim4
-rw-r--r--lib/pure/strutils.nim12
-rw-r--r--lib/pure/times.nim344
-rw-r--r--lib/pure/unittest.nim28
-rw-r--r--lib/pure/uri.nim10
-rw-r--r--lib/pure/xmlparser.nim5
-rw-r--r--lib/stdlib.nimble2
-rw-r--r--lib/system.nim84
-rw-r--r--lib/system/alloc.nim6
-rw-r--r--lib/system/excpt.nim2
-rw-r--r--lib/system/gc.nim7
-rw-r--r--lib/system/gc_stack.nim7
-rw-r--r--lib/system/hti.nim1
-rw-r--r--lib/system/memtracker.nim70
-rw-r--r--lib/system/osalloc.nim13
-rw-r--r--lib/system/sysio.nim27
-rw-r--r--lib/system/sysstr.nim51
-rw-r--r--lib/upcoming/asyncdispatch.nim112
-rw-r--r--lib/wrappers/openssl.nim11
-rw-r--r--lib/wrappers/sqlite3.nim13
-rw-r--r--readme.md5
-rw-r--r--tests/assert/tfailedassert.nim2
-rw-r--r--tests/async/tasyncall.nim9
-rw-r--r--tests/async/tasynceverror.nim66
-rw-r--r--tests/async/tgenericasync.nim14
-rw-r--r--tests/clearmsg/tmacroerrorproc.nim13
-rw-r--r--tests/collections/ttables.nim56
-rw-r--r--tests/generics/tstatictalias.nim20
-rw-r--r--tests/js/tconsole.nim13
-rw-r--r--tests/macros/tdump.nim13
-rw-r--r--tests/manyloc/keineschweine/keineschweine.nim2
-rw-r--r--tests/manyloc/keineschweine/lib/vehicles.nim2
-rw-r--r--tests/method/tmapper.nim2
-rw-r--r--tests/misc/tvarious1.nim8
-rw-r--r--tests/modules/trecinca.nim4
-rw-r--r--tests/modules/trecincb.nim2
-rw-r--r--tests/modules/trecmod.nim6
-rw-r--r--tests/modules/trecmod2.nim7
-rw-r--r--tests/osproc/tworkingdir.nim16
-rw-r--r--tests/parser/tbraces.nim432
-rw-r--r--tests/stdlib/tgetfileinfo.nim40
-rw-r--r--tests/stdlib/tmitems.nim10
-rw-r--r--tests/stdlib/tnilecho.nim2
-rw-r--r--tests/stdlib/tos.nim22
-rw-r--r--tests/stdlib/ttime.nim190
-rw-r--r--tests/system/tdeepcopy.nim95
-rw-r--r--tests/template/tconfusinglocal.nim13
-rw-r--r--tests/test_nimscript.nims2
-rw-r--r--tests/testament/categories.nim17
-rw-r--r--tests/testament/tester.nim2
-rw-r--r--tests/vm/tgorge.bat1
-rw-r--r--tests/vm/tgorge.nim12
-rw-r--r--tests/vm/tgorge.sh2
-rw-r--r--tools/finish.nim167
-rw-r--r--tools/niminst/buildbat.tmpl6
-rw-r--r--tools/niminst/buildsh.tmpl7
-rw-r--r--tools/niminst/debcreation.nim4
-rw-r--r--tools/niminst/deinstall.tmpl2
-rw-r--r--tools/niminst/install.tmpl2
-rw-r--r--tools/niminst/makefile.tmpl4
-rw-r--r--tools/niminst/niminst.nim59
-rw-r--r--tools/niminst/nsis.tmpl2
-rw-r--r--tools/nimsuggest/nimsuggest.nim475
-rw-r--r--tools/nimsuggest/nimsuggest.nim.cfg16
-rw-r--r--tools/nimsuggest/nimsuggest.nimble11
-rw-r--r--tools/nimsuggest/sexp.nim697
-rw-r--r--tools/nimsuggest/tester.nim182
-rw-r--r--tools/nimsuggest/tests/dep_v1.nim8
-rw-r--r--tools/nimsuggest/tests/dep_v2.nim9
-rw-r--r--tools/nimsuggest/tests/tdef1.nim16
-rw-r--r--tools/nimsuggest/tests/tdot1.nim14
-rw-r--r--tools/nimsuggest/tests/tdot2.nim29
-rw-r--r--tools/nimsuggest/tests/tdot3.nim27
-rw-r--r--tools/nimsuggest/tests/tinclude.nim7
-rw-r--r--tools/nimsuggest/tests/tstrutils.nim9
-rw-r--r--tools/nimweb.nim36
-rw-r--r--tools/noprefix.nim32
-rw-r--r--tools/start.bat (renamed from start.bat)6
-rw-r--r--tools/vccenv/vccenv.nim58
-rw-r--r--tools/vccenv/vccexe.nim66
-rw-r--r--tools/website.tmpl8
-rw-r--r--web/assets/niminaction/banner2.pngbin0 -> 71587 bytes
-rw-r--r--web/download.rst39
-rw-r--r--web/news.rst60
-rw-r--r--web/news/e001_version_0_8_6.rst (renamed from web/news/2009_12_21_version_0_8_6_released.rst)0
-rw-r--r--web/news/e002_version_0_8_8.rst (renamed from web/news/2010_03_14_version_0_8_8_released.rst)0
-rw-r--r--web/news/e003_version_0_8_10.rst (renamed from web/news/2010_10_20_version_0_8_10_released.rst)0
-rw-r--r--web/news/e004_version_0_8_12.rst (renamed from web/news/2011_07_10_version_0_8_12_released.rst)0
-rw-r--r--web/news/e005_version_0_8_14.rst (renamed from web/news/2012_02_09_version_0_8_14_released.rst)0
-rw-r--r--web/news/e006_version_0_9_0.rst (renamed from web/news/2012_09_23_version_0_9_0_released.rst)0
-rw-r--r--web/news/e007_version_0_9_2.rst (renamed from web/news/2013_05_20_version_0_9_2_released.rst)0
-rw-r--r--web/news/e008_new_website.rst (renamed from web/news/2013_05_20_new_website_design.rst)0
-rw-r--r--web/news/e009_andreas_rumpfs_talk.rst (renamed from web/news/2014_01_15_andreas_rumpfs_talk_on_nimrod.rst)0
-rw-r--r--web/news/e010_dr_dobbs_journal.rst (renamed from web/news/2014_02_11_nimrod_featured_in_dr_dobbs_journal.rst)0
-rw-r--r--web/news/e011_version_0_9_4.rst (renamed from web/news/2014_04_21_version_0_9_4_released.rst)0
-rw-r--r--web/news/e012_version_0_9_6.rst (renamed from web/news/2014_10_19_version_0_9_6_released.rst)0
-rw-r--r--web/news/e013_version_0_10_2.rst (renamed from web/news/2014_12_29_version_0_10_2_released.rst)0
-rw-r--r--web/news/e014_version_0_11_0.rst (renamed from web/news/2015_04_30_version_0_11_0_released.rst)0
-rw-r--r--web/news/e015_version_0_11_2.rst (renamed from web/news/2015_05_04_version_0_11_2_released.rst)0
-rw-r--r--web/news/e016_nim_conf1.rst (renamed from web/news/2015_10_16_first_nim_conference.rst)0
-rw-r--r--web/news/e017_version_0_12_0.rst (renamed from web/news/2015_10_27_version_0_12_0_released.rst)0
-rw-r--r--web/news/e018_oscon_amsterdam.rst (renamed from web/news/2016_01_18_andreas_rumpfs_talk_at_oscon_amsterdam.rst)0
-rw-r--r--web/news/e019_version_0_13_0.rst (renamed from web/news/2016_01_18_version_0_13_0_released.rst)0
-rw-r--r--web/news/e020_nim_in_action.rst (renamed from web/news/2016_01_27_nim_in_action_is_now_available.rst)0
-rw-r--r--web/news/e021_meet_sponsors.rst (renamed from web/news/2016_06_04_meet_our_bountysource_sponsors.rst)0
-rw-r--r--web/news/e022_version_0_14_0.rst (renamed from web/news/2016_06_07_version_0_14_0_released.rst)0
-rw-r--r--web/news/e023_version_0_14_2.rst (renamed from web/news/2016_06_11_version_0_14_2_released.rst)0
-rw-r--r--web/news/e024_survey.rst (renamed from web/news/2016_06_23_launching_the_2016_nim_community_survey.rst)0
-rw-r--r--web/news/e025_bountysource_update.rst (renamed from web/news/2016_08_06_bountysource_update_the_road_to_v10.rst)0
-rw-r--r--web/news/e026_survey_results.rst (renamed from web/news/2016_09_03_nim_community_survey_results.rst)0
-rw-r--r--web/news/e027_version_0_15_0.rst (renamed from web/news/2016_09_30_version_0_15_0_released.rst)0
-rw-r--r--web/news/e028_version_0_15_2.rst77
-rw-r--r--web/news/e029_version_0_16_0.rst60
-rw-r--r--web/news/e030_nim_in_action_in_production.rst53
-rw-r--r--web/support.rst10
-rw-r--r--web/ticker.html26
-rw-r--r--web/website.ini8
245 files changed, 7457 insertions, 1983 deletions
diff --git a/.gitignore b/.gitignore
index 57b8a68d4..50fa9a431 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ tags
 install.sh
 deinstall.sh
 
+doc/html/
 doc/*.html
 doc/*.pdf
 doc/*.idx
@@ -47,6 +48,7 @@ xcuserdata/
 /testresults.json
 testament.db
 /csources
+dist/
 
 # Private directories and files (IDEs)
 .*/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 54b40dcd7..76c94c8e7 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -40,6 +40,7 @@ deploy-windows:
   artifacts:
     paths:
       - build/*.exe
+      - build/*.zip
     expire_in: 1 week
   tags:
     - windows
diff --git a/.travis.yml b/.travis.yml
index ebf287502..a2ba41e12 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,10 +22,9 @@ script:
   - nim c koch
   - ./koch boot
   - ./koch boot -d:release
-  - nim e install_nimble.nims
+  - ./koch nimble
   - nim e tests/test_nimscript.nims
-  - nimble update
-  - nimble install zip
+  - nimble install zip -y
   - nimble install opengl
   - nimble install sdl1
   - nimble install jester@#head
diff --git a/bootstrap.sh b/bootstrap.sh
deleted file mode 100644
index 5d05ddbf8..000000000
--- a/bootstrap.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-set -e
-set -x
-
-if [ ! -e csources/.git ]; then
-	git clone --depth 1 https://github.com/nim-lang/csources.git csources
-fi
-
-cd "csources"
-sh build.sh
-cd ".."
-
-./bin/nim c koch
-./koch boot -d:release
-./koch geninstall
-
-set +x
-
-echo
-echo 'Install Nim using "./install.sh <dir>" or "sudo ./install.sh <dir>".'
diff --git a/build_tools.sh b/build_tools.sh
deleted file mode 100644
index 4d867b0ad..000000000
--- a/build_tools.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-./bin/nim c --noNimblePath -p:compiler -o:./bin/nimble dist/nimble/src/nimble.nim
-./bin/nim c --noNimblePath -p:compiler -o:./bin/nimsuggest dist/nimsuggest/nimsuggest.nim
-./bin/nim c -o:./bin/nimgrep tools/nimgrep.nim
diff --git a/ci/nsis_build.bat b/ci/nsis_build.bat
index 657b2b90a..4e9d0d67c 100644
--- a/ci/nsis_build.bat
+++ b/ci/nsis_build.bat
@@ -21,11 +21,8 @@ cd web\upload
 move /y docs-%NIMVER%.zip download
 cd ..\..
 
-Rem Build .zip file:
-rem koch csources -d:release
-rem koch xz -d:release
-rem move /y build\nim-%NIMVER%.zip web\upload\download
-
+Rem Build csources
+koch csources -d:release || exit /b
 
 rem Grab C sources and nimsuggest
 git clone --depth 1 https://github.com/nim-lang/csources.git
@@ -46,7 +43,7 @@ koch_temp nsis -d:release || exit /b
 koch_temp zip -d:release || exit /b
 dir build
 move /y build\nim_%NIMVER%.exe build\nim-%NIMVER%_x32.exe || exit /b
-move /y build\nim_%NIMVER%.zip build\nim-%NIMVER%_x32.zip || exit /b
+move /y build\nim-%NIMVER%.zip build\nim-%NIMVER%_x32.zip || exit /b
 
 
 ReM Build Win64 version:
@@ -59,4 +56,4 @@ koch_temp boot -d:release || exit /b
 koch_temp nsis -d:release || exit /b
 koch_temp zip -d:release || exit /b
 move /y build\nim_%NIMVER%.exe build\nim-%NIMVER%_x64.exe || exit /b
-move /y build\nim_%NIMVER%.zip build\nim-%NIMVER%_x64.zip || exit /b
+move /y build\nim-%NIMVER%.zip build\nim-%NIMVER%_x64.zip || exit /b
diff --git a/compiler.nimble b/compiler.nimble
index 66d2ee7c2..d949aa754 100644
--- a/compiler.nimble
+++ b/compiler.nimble
@@ -1,6 +1,6 @@
 [Package]
 name = "compiler"
-version = "0.15.1"
+version = "0.15.3"
 author = "Andreas Rumpf"
 description = "Compiler package providing the compiler sources as a library."
 license = "MIT"
diff --git a/compiler/ast.nim b/compiler/ast.nim
index d8939fc60..8f4acfc3b 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -317,6 +317,10 @@ type
   TTypeKind* = enum  # order is important!
                      # Don't forget to change hti.nim if you make a change here
                      # XXX put this into an include file to avoid this issue!
+                     # several types are no longer used (guess which), but a
+                     # spot in the sequence is kept for backwards compatibility
+                     # (apparently something with bootstrapping)
+                     # if you need to add a type, they can apparently be reused
     tyNone, tyBool, tyChar,
     tyEmpty, tyArrayConstr, tyNil, tyExpr, tyStmt, tyTypeDesc,
     tyGenericInvocation, # ``T[a, b]`` for types to invoke
@@ -345,9 +349,9 @@ type
     tyInt, tyInt8, tyInt16, tyInt32, tyInt64, # signed integers
     tyFloat, tyFloat32, tyFloat64, tyFloat128,
     tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64,
-    tyBigNum,
-    tyConst, tyMutable, tyVarargs,
-    tyIter, # unused
+    tyUnused0, tyUnused1, tyUnused2,
+    tyVarargs,
+    tyUnused,
     tyProxy # used as errornous type (for idetools)
 
     tyBuiltInTypeClass #\
@@ -1046,8 +1050,6 @@ proc newSym*(symKind: TSymKind, name: PIdent, owner: PSym,
 var emptyNode* = newNode(nkEmpty)
 # There is a single empty node that is shared! Do not overwrite it!
 
-var anyGlobal* = newSym(skVar, getIdent("*"), nil, unknownLineInfo())
-
 proc isMetaType*(t: PType): bool =
   return t.kind in tyMetaTypes or
          (t.kind == tyStatic and t.n == nil) or
@@ -1579,14 +1581,6 @@ proc skipStmtList*(n: PNode): PNode =
   else:
     result = n
 
-proc createMagic*(name: string, m: TMagic): PSym =
-  result = newSym(skProc, getIdent(name), nil, unknownLineInfo())
-  result.magic = m
-
-let
-  opNot* = createMagic("not", mNot)
-  opContains* = createMagic("contains", mInSet)
-
 when false:
   proc containsNil*(n: PNode): bool =
     # only for debugging
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index be49ddc87..2761f888b 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -355,6 +355,14 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
     linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src))
   else: internalError("genAssignment: " & $ty.kind)
 
+  if optMemTracker in p.options and dest.s in {OnHeap, OnUnknown}:
+    #writeStackTrace()
+    #echo p.currLineInfo, " requesting"
+    linefmt(p, cpsStmts, "#memTrackerWrite((void*)$1, $2, $3, $4);$n",
+            addrLoc(dest), rope getSize(dest.t),
+            makeCString(p.currLineInfo.toFullPath),
+            rope p.currLineInfo.safeLineNm)
+
 proc genDeepCopy(p: BProc; dest, src: TLoc) =
   var ty = skipTypes(dest.t, abstractVarRange)
   case ty.kind
@@ -592,9 +600,9 @@ proc genEqProc(p: BProc, e: PNode, d: var TLoc) =
 proc genIsNil(p: BProc, e: PNode, d: var TLoc) =
   let t = skipTypes(e.sons[1].typ, abstractRange)
   if t.kind == tyProc and t.callConv == ccClosure:
-    unaryExpr(p, e, d, "$1.ClPrc == 0")
+    unaryExpr(p, e, d, "($1.ClPrc == 0)")
   else:
-    unaryExpr(p, e, d, "$1 == 0")
+    unaryExpr(p, e, d, "($1 == 0)")
 
 proc unaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
   const
@@ -1946,6 +1954,7 @@ proc exprComplexConst(p: BProc, n: PNode, d: var TLoc) =
       d.s = OnStatic
 
 proc expr(p: BProc, n: PNode, d: var TLoc) =
+  p.currLineInfo = n.info
   case n.kind
   of nkSym:
     var sym = n.sym
diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim
index eac734b3d..60ee0eaee 100644
--- a/compiler/ccgtypes.nim
+++ b/compiler/ccgtypes.nim
@@ -122,7 +122,7 @@ proc mapType(typ: PType): TCTypeKind =
   of tyOpenArray, tyArrayConstr, tyArray, tyVarargs: result = ctArray
   of tyObject, tyTuple: result = ctStruct
   of tyGenericBody, tyGenericInst, tyGenericParam, tyDistinct, tyOrdinal,
-     tyConst, tyMutable, tyIter, tyTypeDesc:
+     tyTypeDesc:
     result = mapType(lastSon(typ))
   of tyEnum:
     if firstOrd(typ) < 0:
@@ -206,6 +206,10 @@ proc cacheGetType(tab: TIdTable, key: PType): Rope =
   # linear search is not necessary anymore:
   result = Rope(idTableGet(tab, key))
 
+proc addAbiCheck(m: BModule, t: PType, name: Rope) =
+  if isDefined("checkabi"):
+    addf(m.s[cfsTypeInfo], "NIM_CHECK_SIZE($1, $2);$n", [name, rope(getSize(t))])
+
 proc getTempName(m: BModule): Rope =
   result = m.tmpBase & rope(m.labels)
   inc m.labels
@@ -267,6 +271,11 @@ proc getSimpleTypeDesc(m: BModule, typ: PType): Rope =
     result = getSimpleTypeDesc(m, lastSon typ)
   else: result = nil
 
+  if result != nil and typ.isImportedType():
+    if cacheGetType(m.typeCache, typ) == nil:
+      idTablePut(m.typeCache, typ, result)
+      addAbiCheck(m, typ, result)
+
 proc pushType(m: BModule, typ: PType) =
   add(m.typeStack, typ)
 
@@ -656,6 +665,7 @@ proc getTypeDescAux(m: BModule, typ: PType, check: var IntSet): Rope =
       let foo = getTypeDescAux(m, t.sons[1], check)
       addf(m.s[cfsTypes], "typedef $1 $2[$3];$n",
            [foo, result, rope(n)])
+    else: addAbiCheck(m, t, result)
   of tyObject, tyTuple:
     if isImportedCppType(t) and typ.kind == tyGenericInst:
       # for instantiated templates we do not go through the type cache as the
@@ -701,7 +711,9 @@ proc getTypeDescAux(m: BModule, typ: PType, check: var IntSet): Rope =
       idTablePut(m.typeCache, t, result) # always call for sideeffects:
       let recdesc = if t.kind != tyTuple: getRecordDesc(m, t, result, check)
                     else: getTupleDesc(m, t, result, check)
-      if not isImportedType(t): add(m.s[cfsTypes], recdesc)
+      if not isImportedType(t):
+        add(m.s[cfsTypes], recdesc)
+      elif tfIncompleteStruct notin t.flags: addAbiCheck(m, t, result)
   of tySet:
     result = getTypeName(t.lastSon) & "Set"
     idTablePut(m.typeCache, t, result)
@@ -711,8 +723,7 @@ proc getTypeDescAux(m: BModule, typ: PType, check: var IntSet): Rope =
       of 1, 2, 4, 8: addf(m.s[cfsTypes], "typedef NU$2 $1;$n", [result, rope(s*8)])
       else: addf(m.s[cfsTypes], "typedef NU8 $1[$2];$n",
              [result, rope(getSize(t))])
-  of tyGenericInst, tyDistinct, tyOrdinal, tyConst, tyMutable,
-      tyIter, tyTypeDesc:
+  of tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc:
     result = getTypeDescAux(m, lastSon(t), check)
   else:
     internalError("getTypeDescAux(" & $t.kind & ')')
diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim
index ecd98a2bf..2216cb4fd 100644
--- a/compiler/ccgutils.nim
+++ b/compiler/ccgutils.nim
@@ -93,7 +93,7 @@ proc getUniqueType*(key: PType): PType =
     # produced instead of ``NI``.
     result = key
   of  tyEmpty, tyNil, tyExpr, tyStmt, tyPointer, tyString,
-      tyCString, tyNone, tyBigNum, tyVoid:
+      tyCString, tyNone, tyVoid:
     result = gCanonicalTypes[k]
     if result == nil:
       gCanonicalTypes[k] = key
@@ -106,7 +106,7 @@ proc getUniqueType*(key: PType): PType =
   of tyDistinct:
     if key.deepCopy != nil: result = key
     else: result = getUniqueType(lastSon(key))
-  of tyGenericInst, tyOrdinal, tyMutable, tyConst, tyIter, tyStatic:
+  of tyGenericInst, tyOrdinal, tyStatic:
     result = getUniqueType(lastSon(key))
     #let obj = lastSon(key)
     #if obj.sym != nil and obj.sym.name.s == "TOption":
@@ -153,6 +153,7 @@ proc getUniqueType*(key: PType): PType =
     else:
       # ugh, we need the canon here:
       result = slowSearch(key, k)
+  of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("getUniqueType")
 
 proc tableGetType*(tab: TIdTable, key: PType): RootRef =
   # returns nil if we need to declare this type
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index d80a68609..6e18c8389 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -16,6 +16,8 @@ import
   condsyms, rodutils, renderer, idgen, cgendata, ccgmerge, semfold, aliases,
   lowerings, semparallel
 
+from modulegraphs import ModuleGraph
+
 import strutils except `%` # collides with ropes.`%`
 
 when options.hasTinyCBackend:
@@ -870,7 +872,7 @@ proc genMainProc(m: BModule) =
     NimMainInner = "N_CDECL(void, NimMainInner)(void) {$N" &
         "$1" &
       "}$N$N"
-      
+
     NimMainProc =
       "N_CDECL(void, NimMain)(void) {$N" &
         "\tvoid (*volatile inner)();$N" &
@@ -972,7 +974,13 @@ proc getSomeInitName(m: PSym, suffix: string): Rope =
   result.add m.name.s
   result.add suffix
 
-proc getInitName(m: PSym): Rope = getSomeInitName(m, "Init000")
+proc getInitName(m: PSym): Rope =
+  if sfMainModule in m.flags:
+    # generate constant name for main module, for "easy" debugging.
+    result = rope"NimMainModule"
+  else:
+    result = getSomeInitName(m, "Init000")
+
 proc getDatInitName(m: PSym): Rope = getSomeInitName(m, "DatInit000")
 
 proc registerModuleToMain(m: PSym) =
@@ -1168,7 +1176,7 @@ proc newModule(module: PSym): BModule =
     if (sfDeadCodeElim in module.flags):
       internalError("added pending module twice: " & module.filename)
 
-proc myOpen(module: PSym): PPassContext =
+proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
   result = newModule(module)
   if optGenIndex in gGlobalOptions and generatedHeader == nil:
     let f = if headerFile.len > 0: headerFile else: gProjectFull
@@ -1203,7 +1211,7 @@ proc getCFile(m: BModule): string =
       else: ".c"
   result = changeFileExt(completeCFilePath(m.cfilename.withPackageName), ext)
 
-proc myOpenCached(module: PSym, rd: PRodReader): PPassContext =
+proc myOpenCached(graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext =
   assert optSymbolFiles in gGlobalOptions
   var m = newModule(module)
   readMergeInfo(getCFile(m), m)
diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim
index a94950029..faeea7afb 100644
--- a/compiler/cgendata.nim
+++ b/compiler/cgendata.nim
@@ -68,6 +68,7 @@ type
     beforeRetNeeded*: bool    # true iff 'BeforeRet' label for proc is needed
     threadVarAccessed*: bool  # true if the proc already accessed some threadvar
     lastLineInfo*: TLineInfo  # to avoid generating excessive 'nimln' statements
+    currLineInfo*: TLineInfo  # AST codegen will make this superfluous
     nestedTryStmts*: seq[PNode]   # in how many nested try statements we are
                                   # (the vars must be volatile then)
     inExceptBlock*: int       # are we currently inside an except block?
diff --git a/compiler/cgmeth.nim b/compiler/cgmeth.nim
index bcf0b535b..5f0d71cc6 100644
--- a/compiler/cgmeth.nim
+++ b/compiler/cgmeth.nim
@@ -165,8 +165,9 @@ proc methodDef*(s: PSym, fromCache: bool) =
       if witness.isNil: witness = gMethods[i].methods[0]
   # create a new dispatcher:
   add(gMethods, (methods: @[s], dispatcher: createDispatcher(s)))
-  if fromCache:
-    internalError(s.info, "no method dispatcher found")
+  #echo "adding ", s.info
+  #if fromCache:
+  #  internalError(s.info, "no method dispatcher found")
   if witness != nil:
     localError(s.info, "invalid declaration order; cannot attach '" & s.name.s &
                        "' to method defined here: " & $witness.info)
diff --git a/compiler/commands.nim b/compiler/commands.nim
index de1197292..590c4871d 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -124,17 +124,17 @@ proc splitSwitch(switch: string, cmd, arg: var string, pass: TCmdLinePass,
 
 proc processOnOffSwitch(op: TOptions, arg: string, pass: TCmdLinePass,
                         info: TLineInfo) =
-  case whichKeyword(arg)
-  of wOn: gOptions = gOptions + op
-  of wOff: gOptions = gOptions - op
+  case arg.normalize
+  of "on": gOptions = gOptions + op
+  of "off": gOptions = gOptions - op
   else: localError(info, errOnOrOffExpectedButXFound, arg)
 
 proc processOnOffSwitchOrList(op: TOptions, arg: string, pass: TCmdLinePass,
                               info: TLineInfo): bool =
   result = false
-  case whichKeyword(arg)
-  of wOn: gOptions = gOptions + op
-  of wOff: gOptions = gOptions - op
+  case arg.normalize
+  of "on": gOptions = gOptions + op
+  of "off": gOptions = gOptions - op
   else:
     if arg == "list":
       result = true
@@ -143,9 +143,9 @@ proc processOnOffSwitchOrList(op: TOptions, arg: string, pass: TCmdLinePass,
 
 proc processOnOffSwitchG(op: TGlobalOptions, arg: string, pass: TCmdLinePass,
                          info: TLineInfo) =
-  case whichKeyword(arg)
-  of wOn: gGlobalOptions = gGlobalOptions + op
-  of wOff: gGlobalOptions = gGlobalOptions - op
+  case arg.normalize
+  of "on": gGlobalOptions = gGlobalOptions + op
+  of "off": gGlobalOptions = gGlobalOptions - op
   else: localError(info, errOnOrOffExpectedButXFound, arg)
 
 proc expectArg(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
@@ -178,12 +178,12 @@ proc processSpecificNote*(arg: string, state: TSpecialWord, pass: TCmdLinePass,
     var x = findStr(msgs.WarningsToStr, id)
     if x >= 0: n = TNoteKind(x + ord(warnMin))
     else: localError(info, "unknown warning: " & id)
-  case whichKeyword(substr(arg, i))
-  of wOn:
+  case substr(arg, i).normalize
+  of "on":
     incl(gNotes, n)
     incl(gMainPackageNotes, n)
     incl(enableNotes, n)
-  of wOff:
+  of "off":
     excl(gNotes, n)
     excl(gMainPackageNotes, n)
     incl(disableNotes, n)
@@ -242,6 +242,7 @@ proc testCompileOption*(switch: string, info: TLineInfo): bool =
   of "linetrace": result = contains(gOptions, optLineTrace)
   of "debugger": result = contains(gOptions, optEndb)
   of "profiler": result = contains(gOptions, optProfiler)
+  of "memtracker": result = contains(gOptions, optMemTracker)
   of "checks", "x": result = gOptions * ChecksOptions == ChecksOptions
   of "floatchecks":
     result = gOptions * {optNaNCheck, optInfCheck} == {optNaNCheck, optInfCheck}
@@ -264,6 +265,7 @@ proc testCompileOption*(switch: string, info: TLineInfo): bool =
   of "implicitstatic": result = contains(gOptions, optImplicitStatic)
   of "patterns": result = contains(gOptions, optPatterns)
   of "experimental": result = gExperimentalMode
+  of "excessivestacktrace": result = contains(gGlobalOptions, optExcessiveStackTrace)
   else: invalidCmdLineOption(passCmd1, switch, info)
 
 proc processPath(path: string, info: TLineInfo,
@@ -445,6 +447,10 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
     processOnOffSwitch({optProfiler}, arg, pass, info)
     if optProfiler in gOptions: defineSymbol("profiler")
     else: undefSymbol("profiler")
+  of "memtracker":
+    processOnOffSwitch({optMemTracker}, arg, pass, info)
+    if optMemTracker in gOptions: defineSymbol("memtracker")
+    else: undefSymbol("memtracker")
   of "checks", "x": processOnOffSwitch(ChecksOptions, arg, pass, info)
   of "floatchecks":
     processOnOffSwitch({optNaNCheck, optInfCheck}, arg, pass, info)
@@ -630,12 +636,8 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
   of "dynliboverride":
     dynlibOverride(switch, arg, pass, info)
   of "cs":
+    # only supported for compatibility. Does nothing.
     expectArg(switch, arg, pass, info)
-    case arg
-    of "partial": idents.firstCharIsCS = true
-    of "none": idents.firstCharIsCS = false
-    else: localError(info, errGenerated,
-      "'partial' or 'none' expected, but found " & arg)
   of "experimental":
     expectNoArg(switch, arg, pass, info)
     gExperimentalMode = true
diff --git a/compiler/depends.nim b/compiler/depends.nim
index 1ccb134f2..9087f89f2 100644
--- a/compiler/depends.nim
+++ b/compiler/depends.nim
@@ -12,6 +12,8 @@
 import
   os, options, ast, astalgo, msgs, ropes, idents, passes, importer
 
+from modulegraphs import ModuleGraph
+
 proc generateDot*(project: string)
 
 type
@@ -46,7 +48,7 @@ proc generateDot(project: string) =
       rope(changeFileExt(extractFilename(project), "")), gDotGraph],
             changeFileExt(project, "dot"))
 
-proc myOpen(module: PSym): PPassContext =
+proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
   var g: PGen
   new(g)
   g.module = module
diff --git a/compiler/docgen.nim b/compiler/docgen.nim
index c220902ff..76b36d796 100644
--- a/compiler/docgen.nim
+++ b/compiler/docgen.nim
@@ -184,7 +184,7 @@ proc genRecComment(d: PDoc, n: PNode): Rope =
   if n == nil: return nil
   result = genComment(d, n).rope
   if result == nil:
-    if n.kind notin {nkEmpty..nkNilLit, nkEnumTy}:
+    if n.kind notin {nkEmpty..nkNilLit, nkEnumTy, nkTupleTy}:
       for i in countup(0, len(n)-1):
         result = genRecComment(d, n.sons[i])
         if result != nil: return
@@ -514,7 +514,7 @@ proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode =
     result["code"] = %r.buf
 
 proc checkForFalse(n: PNode): bool =
-  result = n.kind == nkIdent and identEq(n.ident, "false")
+  result = n.kind == nkIdent and cmpIgnoreStyle(n.ident.s, "false") == 0
 
 proc traceDeps(d: PDoc, n: PNode) =
   const k = skModule
@@ -691,7 +691,7 @@ proc writeOutputJson*(d: PDoc, filename, outExt: string,
       discard "fixme: error report"
 
 proc commandDoc*() =
-  var ast = parseFile(gProjectMainIdx)
+  var ast = parseFile(gProjectMainIdx, newIdentCache())
   if ast == nil: return
   var d = newDocumentor(gProjectFull, options.gConfigVars)
   d.hasToc = true
@@ -721,7 +721,7 @@ proc commandRst2TeX*() =
   commandRstAux(gProjectFull, TexExt)
 
 proc commandJson*() =
-  var ast = parseFile(gProjectMainIdx)
+  var ast = parseFile(gProjectMainIdx, newIdentCache())
   if ast == nil: return
   var d = newDocumentor(gProjectFull, options.gConfigVars)
   d.hasToc = true
diff --git a/compiler/docgen2.nim b/compiler/docgen2.nim
index d70d5406c..9504ab52f 100644
--- a/compiler/docgen2.nim
+++ b/compiler/docgen2.nim
@@ -13,6 +13,8 @@
 import
   os, options, ast, astalgo, msgs, ropes, idents, passes, docgen
 
+from modulegraphs import ModuleGraph
+
 type
   TGen = object of TPassContext
     doc: PDoc
@@ -49,7 +51,7 @@ proc processNodeJson(c: PPassContext, n: PNode): PNode =
   var g = PGen(c)
   generateJson(g.doc, n)
 
-proc myOpen(module: PSym): PPassContext =
+proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
   var g: PGen
   new(g)
   g.module = module
diff --git a/compiler/filter_tmpl.nim b/compiler/filter_tmpl.nim
index 9e123e3a1..361b3d276 100644
--- a/compiler/filter_tmpl.nim
+++ b/compiler/filter_tmpl.nim
@@ -62,10 +62,7 @@ proc withInExpr(p: TTmplParser): bool {.inline.} =
   result = p.par > 0 or p.bracket > 0 or p.curly > 0
 
 proc parseLine(p: var TTmplParser) =
-  var
-    d, j, curly: int
-    keyw: string
-  j = 0
+  var j = 0
   while p.x[j] == ' ': inc(j)
   if p.x[0] == p.nimDirective and p.x[1] == '?':
     newLine(p)
@@ -73,16 +70,16 @@ proc parseLine(p: var TTmplParser) =
     newLine(p)
     inc(j)
     while p.x[j] == ' ': inc(j)
-    d = j
-    keyw = ""
+    let d = j
+    var keyw = ""
     while p.x[j] in PatternChars:
       add(keyw, p.x[j])
       inc(j)
 
     scanPar(p, j)
     p.pendingExprLine = withInExpr(p) or llstream.endsWithOpr(p.x)
-    case whichKeyword(keyw)
-    of wEnd:
+    case keyw
+    of "end":
       if p.indent >= 2:
         dec(p.indent, 2)
       else:
@@ -90,15 +87,15 @@ proc parseLine(p: var TTmplParser) =
         localError(p.info, errXNotAllowedHere, "end")
       llStreamWrite(p.outp, spaces(p.indent))
       llStreamWrite(p.outp, "#end")
-    of wIf, wWhen, wTry, wWhile, wFor, wBlock, wCase, wProc, wIterator,
-       wConverter, wMacro, wTemplate, wMethod:
+    of "if", "when", "try", "while", "for", "block", "case", "proc", "iterator",
+       "converter", "macro", "template", "method":
       llStreamWrite(p.outp, spaces(p.indent))
       llStreamWrite(p.outp, substr(p.x, d))
       inc(p.indent, 2)
-    of wElif, wOf, wElse, wExcept, wFinally:
+    of "elif", "of", "else", "except", "finally":
       llStreamWrite(p.outp, spaces(p.indent - 2))
       llStreamWrite(p.outp, substr(p.x, d))
-    of wLet, wVar, wConst, wType:
+    of "wLet", "wVar", "wConst", "wType":
       llStreamWrite(p.outp, spaces(p.indent))
       llStreamWrite(p.outp, substr(p.x, d))
       if not p.x.contains({':', '='}):
@@ -158,7 +155,7 @@ proc parseLine(p: var TTmplParser) =
             llStreamWrite(p.outp, p.toStr)
             llStreamWrite(p.outp, '(')
             inc(j)
-            curly = 0
+            var curly = 0
             while true:
               case p.x[j]
               of '\0':
diff --git a/compiler/filters.nim b/compiler/filters.nim
index adafe464e..d1a6409ff 100644
--- a/compiler/filters.nim
+++ b/compiler/filters.nim
@@ -30,7 +30,7 @@ proc getArg(n: PNode, name: string, pos: int): PNode =
   for i in countup(1, sonsLen(n) - 1):
     if n.sons[i].kind == nkExprEqExpr:
       if n.sons[i].sons[0].kind != nkIdent: invalidPragma(n)
-      if identEq(n.sons[i].sons[0].ident, name):
+      if cmpIgnoreStyle(n.sons[i].sons[0].ident.s, name) == 0:
         return n.sons[i].sons[1]
     elif i == pos:
       return n.sons[i]
@@ -50,8 +50,8 @@ proc strArg(n: PNode, name: string, pos: int, default: string): string =
 proc boolArg(n: PNode, name: string, pos: int, default: bool): bool =
   var x = getArg(n, name, pos)
   if x == nil: result = default
-  elif (x.kind == nkIdent) and identEq(x.ident, "true"): result = true
-  elif (x.kind == nkIdent) and identEq(x.ident, "false"): result = false
+  elif x.kind == nkIdent and cmpIgnoreStyle(x.ident.s, "true") == 0: result = true
+  elif x.kind == nkIdent and cmpIgnoreStyle(x.ident.s, "false") == 0: result = false
   else: invalidPragma(n)
 
 proc filterStrip(stdin: PLLStream, filename: string, call: PNode): PLLStream =
@@ -62,7 +62,7 @@ proc filterStrip(stdin: PLLStream, filename: string, call: PNode): PLLStream =
   var line = newStringOfCap(80)
   while llStreamReadLine(stdin, line):
     var stripped = strip(line, leading, trailing)
-    if (len(pattern) == 0) or startsWith(stripped, pattern):
+    if len(pattern) == 0 or startsWith(stripped, pattern):
       llStreamWriteln(result, stripped)
     else:
       llStreamWriteln(result, line)
diff --git a/compiler/idents.nim b/compiler/idents.nim
index d9b72baf0..eecfa60a1 100644
--- a/compiler/idents.nim
+++ b/compiler/idents.nim
@@ -12,7 +12,7 @@
 # id. This module is essential for the compiler's performance.
 
 import
-  hashes, strutils, etcpriv
+  hashes, strutils, etcpriv, wordrecg
 
 type
   TIdObj* = object of RootObj
@@ -25,12 +25,20 @@ type
     next*: PIdent             # for hash-table chaining
     h*: Hash                 # hash value of s
 
-var firstCharIsCS*: bool = true
-var buckets*: array[0..4096 * 2 - 1, PIdent]
+  IdentCache* = ref object
+    buckets: array[0..4096 * 2 - 1, PIdent]
+    wordCounter: int
+    idAnon*, idDelegator*, emptyIdent*: PIdent
+
+var
+  legacy: IdentCache
+
+proc resetIdentCache*() =
+  for i in low(legacy.buckets)..high(legacy.buckets):
+    legacy.buckets[i] = nil
 
 proc cmpIgnoreStyle(a, b: cstring, blen: int): int =
-  if firstCharIsCS:
-    if a[0] != b[0]: return 1
+  if a[0] != b[0]: return 1
   var i = 0
   var j = 0
   result = 1
@@ -65,9 +73,9 @@ proc cmpExact(a, b: cstring, blen: int): int =
   if result == 0:
     if a[i] != '\0': result = 1
 
-var wordCounter = 1
+{.this: self.}
 
-proc getIdent*(identifier: cstring, length: int, h: Hash): PIdent =
+proc getIdent*(self: IdentCache; identifier: cstring, length: int, h: Hash): PIdent =
   var idx = h and high(buckets)
   result = buckets[idx]
   var last: PIdent = nil
@@ -97,16 +105,33 @@ proc getIdent*(identifier: cstring, length: int, h: Hash): PIdent =
   else:
     result.id = id
 
-proc getIdent*(identifier: string): PIdent =
+proc getIdent*(self: IdentCache; identifier: string): PIdent =
   result = getIdent(cstring(identifier), len(identifier),
                     hashIgnoreStyle(identifier))
 
-proc getIdent*(identifier: string, h: Hash): PIdent =
+proc getIdent*(self: IdentCache; identifier: string, h: Hash): PIdent =
   result = getIdent(cstring(identifier), len(identifier), h)
 
-proc identEq*(id: PIdent, name: string): bool =
-  result = id.id == getIdent(name).id
+proc newIdentCache*(): IdentCache =
+  if legacy.isNil:
+    result = IdentCache()
+    result.idAnon = result.getIdent":anonymous"
+    result.wordCounter = 1
+    result.idDelegator = result.getIdent":delegator"
+    result.emptyIdent = result.getIdent("")
+    # initialize the keywords:
+    for s in countup(succ(low(specialWords)), high(specialWords)):
+      result.getIdent(specialWords[s], hashIgnoreStyle(specialWords[s])).id = ord(s)
+    legacy = result
+  else:
+    result = legacy
 
-var idAnon* = getIdent":anonymous"
-let idDelegator* = getIdent":delegator"
+proc whichKeyword*(id: PIdent): TSpecialWord =
+  if id.id < 0: result = wInvalid
+  else: result = TSpecialWord(id.id)
 
+proc getIdent*(identifier: string): PIdent =
+  ## for backwards compatibility.
+  if legacy.isNil:
+    discard newIdentCache()
+  legacy.getIdent identifier
diff --git a/compiler/importer.nim b/compiler/importer.nim
index 87415733b..feebf97c4 100644
--- a/compiler/importer.nim
+++ b/compiler/importer.nim
@@ -100,7 +100,7 @@ proc importSymbol(c: PContext, n: PNode, fromMod: PSym) =
   let ident = lookups.considerQuotedIdent(n)
   let s = strTableGet(fromMod.tab, ident)
   if s == nil:
-    localError(n.info, errUndeclaredIdentifier, ident.s)
+    errorUndeclaredIdentifier(c, n.info, ident.s)
   else:
     if s.kind == skStub: loadStub(s)
     if s.kind notin ExportableSymKinds:
@@ -162,12 +162,26 @@ proc importModuleAs(n: PNode, realModule: PSym): PSym =
 proc myImportModule(c: PContext, n: PNode): PSym =
   var f = checkModuleName(n)
   if f != InvalidFileIDX:
-    result = importModuleAs(n, gImportModule(c.module, f))
+    let L = c.graph.importStack.len
+    let recursion = c.graph.importStack.find(f)
+    c.graph.importStack.add f
+    #echo "adding ", toFullPath(f), " at ", L+1
+    if recursion >= 0:
+      var err = ""
+      for i in countup(recursion, L-1):
+        if i > recursion: err.add "\n"
+        err.add toFullPath(c.graph.importStack[i]) & " imports " &
+                toFullPath(c.graph.importStack[i+1])
+      c.recursiveDep = err
+    result = importModuleAs(n, gImportModule(c.graph, c.module, f, c.cache))
+    #echo "set back to ", L
+    c.graph.importStack.setLen(L)
     # we cannot perform this check reliably because of
     # test: modules/import_in_config)
-    if result.info.fileIndex == c.module.info.fileIndex and
-        result.info.fileIndex == n.info.fileIndex:
-      localError(n.info, errGenerated, "A module cannot import itself")
+    when true:
+      if result.info.fileIndex == c.module.info.fileIndex and
+          result.info.fileIndex == n.info.fileIndex:
+        localError(n.info, errGenerated, "A module cannot import itself")
     if sfDeprecated in result.flags:
       message(n.info, warnDeprecated, result.name.s)
     #suggestSym(n.info, result, false)
diff --git a/compiler/installer.ini b/compiler/installer.ini
index 2a9fa36a5..5e4b3ddbc 100644
--- a/compiler/installer.ini
+++ b/compiler/installer.ini
@@ -41,8 +41,8 @@ Files: "config/nimdoc.tex.cfg"
 ; Files: "doc/*.cfg"
 ; Files: "doc/*.pdf"
 ; Files: "doc/*.ini"
-Files: "doc/overview.html"
-Start: "doc/overview.html"
+Files: "doc/html/overview.html"
+Start: "doc/html/overview.html"
 
 
 [Other]
@@ -63,6 +63,7 @@ Files: "icons/koch_icon.o"
 
 Files: "compiler"
 Files: "doc"
+Files: "doc/html"
 Files: "tools"
 Files: "web/website.ini"
 Files: "web/ticker.html"
@@ -89,12 +90,11 @@ Files: "bin/c2nim.exe"
 Files: "bin/nimgrep.exe"
 Files: "bin/nimsuggest.exe"
 Files: "bin/nimble.exe"
-Files: "bin/makelink.exe"
-Files: "bin/*.dll"
 
 Files: "koch.exe"
+Files: "finish.exe"
 ; Files: "dist/mingw"
-Files: "start.bat"
+Files: r"tools\start.bat"
 BinPath: r"bin;dist\mingw\bin;dist"
 
 ;           Section | dir | zipFile | size hint (in KB) | url | exe start menu entry
@@ -104,6 +104,10 @@ Download: r"Support DLLs|bin|nim_dlls.zip|479|http://nim-lang.org/download/dlls.
 Download: r"Aporia Text Editor|dist|aporia.zip|97997|http://nim-lang.org/download/aporia-0.4.0.zip|aporia-0.4.0\bin\aporia.exe"
 ; for now only NSIS supports optional downloads
 
+[WinBin]
+Files: "$NIMINSTDEPS/makelink.exe"
+Files: "$NIMINSTDEPS/*.dll"
+
 [UnixBin]
 Files: "bin/nim"
 
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index e7fe8cc27..028dd00f0 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -35,6 +35,8 @@ import
   times, ropes, math, passes, ccgutils, wordrecg, renderer, rodread, rodutils,
   intsets, cgmeth, lowerings
 
+from modulegraphs import ModuleGraph
+
 type
   TTarget = enum
     targetJS, targetPHP
@@ -138,7 +140,7 @@ proc declareGlobal(p: PProc; id: int; r: Rope) =
 
 const
   MappedToObject = {tyObject, tyArray, tyArrayConstr, tyTuple, tyOpenArray,
-    tySet, tyBigNum, tyVarargs}
+    tySet, tyVarargs}
 
 proc mapType(typ: PType): TJSTypeKind =
   let t = skipTypes(typ, abstractInst)
@@ -151,15 +153,13 @@ proc mapType(typ: PType): TJSTypeKind =
   of tyPointer:
     # treat a tyPointer like a typed pointer to an array of bytes
     result = etyBaseIndex
-  of tyRange, tyDistinct, tyOrdinal, tyConst, tyMutable, tyIter, tyProxy:
-    result = mapType(t.sons[0])
+  of tyRange, tyDistinct, tyOrdinal, tyProxy: result = mapType(t.sons[0])
   of tyInt..tyInt64, tyUInt..tyUInt64, tyEnum, tyChar: result = etyInt
   of tyBool: result = etyBool
   of tyFloat..tyFloat128: result = etyFloat
   of tySet: result = etyObject # map a set to a table
   of tyString, tySequence: result = etySeq
-  of tyObject, tyArray, tyArrayConstr, tyTuple, tyOpenArray, tyBigNum,
-     tyVarargs:
+  of tyObject, tyArray, tyArrayConstr, tyTuple, tyOpenArray, tyVarargs:
     result = etyObject
   of tyNil: result = etyNull
   of tyGenericInst, tyGenericParam, tyGenericBody, tyGenericInvocation,
@@ -171,6 +171,7 @@ proc mapType(typ: PType): TJSTypeKind =
     else: result = etyNone
   of tyProc: result = etyProc
   of tyCString: result = etyString
+  of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("mapType")
 
 proc mapType(p: PProc; typ: PType): TJSTypeKind =
   if p.target == targetPHP: result = etyObject
@@ -2273,11 +2274,11 @@ proc myClose(b: PPassContext, n: PNode): PNode =
     for obj, content in items(globals.classes):
       genClass(obj, content, ext)
 
-proc myOpenCached(s: PSym, rd: PRodReader): PPassContext =
+proc myOpenCached(graph: ModuleGraph; s: PSym, rd: PRodReader): PPassContext =
   internalError("symbol files are not possible with the JS code generator")
   result = nil
 
-proc myOpen(s: PSym): PPassContext =
+proc myOpen(graph: ModuleGraph; s: PSym; cache: IdentCache): PPassContext =
   var r = newModule(s)
   r.target = if gCmd == cmdCompileToPHP: targetPHP else: targetJS
   result = r
diff --git a/compiler/lexer.nim b/compiler/lexer.nim
index 9c513034b..2769d757c 100644
--- a/compiler/lexer.nim
+++ b/compiler/lexer.nim
@@ -127,8 +127,9 @@ type
                               # this is needed because scanning comments
                               # needs so much look-ahead
     currLineIndent*: int
-    strongSpaces*: bool
+    strongSpaces*, allowTabs*: bool
     errorHandler*: TErrorHandler
+    cache*: IdentCache
 
 var gLinesCompiled*: int  # all lines that have been compiled
 
@@ -164,7 +165,6 @@ proc tokToStr*(tok: TToken): string =
     if tok.ident != nil:
       result = tok.ident.s
     else:
-      internalError("tokToStr")
       result = ""
 
 proc prettyTok*(tok: TToken): string =
@@ -175,8 +175,6 @@ proc printTok*(tok: TToken) =
   msgWriteln($tok.line & ":" & $tok.col & "\t" &
       TokTypeToStr[tok.tokType] & " " & tokToStr(tok))
 
-var dummyIdent: PIdent
-
 proc initToken*(L: var TToken) =
   L.tokType = tkInvalid
   L.iNumber = 0
@@ -185,7 +183,7 @@ proc initToken*(L: var TToken) =
   L.literal = ""
   L.fNumber = 0.0
   L.base = base10
-  L.ident = dummyIdent
+  L.ident = nil
 
 proc fillToken(L: var TToken) =
   L.tokType = tkInvalid
@@ -195,17 +193,20 @@ proc fillToken(L: var TToken) =
   setLen(L.literal, 0)
   L.fNumber = 0.0
   L.base = base10
-  L.ident = dummyIdent
+  L.ident = nil
 
-proc openLexer*(lex: var TLexer, fileIdx: int32, inputstream: PLLStream) =
+proc openLexer*(lex: var TLexer, fileIdx: int32, inputstream: PLLStream;
+                 cache: IdentCache) =
   openBaseLexer(lex, inputstream)
   lex.fileIdx = fileidx
   lex.indentAhead = - 1
   lex.currLineIndent = 0
   inc(lex.lineNumber, inputstream.lineOffset)
+  lex.cache = cache
 
-proc openLexer*(lex: var TLexer, filename: string, inputstream: PLLStream) =
-  openLexer(lex, filename.fileInfoIdx, inputstream)
+proc openLexer*(lex: var TLexer, filename: string, inputstream: PLLStream;
+                cache: IdentCache) =
+  openLexer(lex, filename.fileInfoIdx, inputstream, cache)
 
 proc closeLexer*(lex: var TLexer) =
   inc(gLinesCompiled, lex.lineNumber)
@@ -746,7 +747,7 @@ proc getSymbol(L: var TLexer, tok: var TToken) =
 
     else: break
   h = !$h
-  tok.ident = getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h)
+  tok.ident = L.cache.getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h)
   L.bufpos = pos
   if (tok.ident.id < ord(tokKeywordLow) - ord(tkSymbol)) or
       (tok.ident.id > ord(tokKeywordHigh) - ord(tkSymbol)):
@@ -757,7 +758,7 @@ proc getSymbol(L: var TLexer, tok: var TToken) =
 proc endOperator(L: var TLexer, tok: var TToken, pos: int,
                  hash: Hash) {.inline.} =
   var h = !$hash
-  tok.ident = getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h)
+  tok.ident = L.cache.getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h)
   if (tok.ident.id < oprLow) or (tok.ident.id > oprHigh): tok.tokType = tkOpr
   else: tok.tokType = TTokType(tok.ident.id - oprLow + ord(tkColon))
   L.bufpos = pos
@@ -847,34 +848,23 @@ proc scanComment(L: var TLexer, tok: var TToken) =
   tok.tokType = tkComment
   # iNumber contains the number of '\n' in the token
   tok.iNumber = 0
-  when not defined(nimfix):
-    assert buf[pos+1] == '#'
-    if buf[pos+2] == '[':
-      skipMultiLineComment(L, tok, pos+3, true)
-      return
-    inc(pos, 2)
+  assert buf[pos+1] == '#'
+  if buf[pos+2] == '[':
+    skipMultiLineComment(L, tok, pos+3, true)
+    return
+  inc(pos, 2)
 
   var toStrip = 0
   while buf[pos] == ' ':
     inc pos
     inc toStrip
 
-  when defined(nimfix):
-    var col = getColNumber(L, pos)
   while true:
     var lastBackslash = -1
     while buf[pos] notin {CR, LF, nimlexbase.EndOfFile}:
       if buf[pos] == '\\': lastBackslash = pos+1
       add(tok.literal, buf[pos])
       inc(pos)
-    when defined(nimfix):
-      if lastBackslash > 0:
-        # a backslash is a continuation character if only followed by spaces
-        # plus a newline:
-        while buf[lastBackslash] == ' ': inc(lastBackslash)
-        if buf[lastBackslash] notin {CR, LF, nimlexbase.EndOfFile}:
-          # false positive:
-          lastBackslash = -1
 
     pos = handleCRLF(L, pos)
     buf = L.buf
@@ -883,21 +873,13 @@ proc scanComment(L: var TLexer, tok: var TToken) =
       inc(pos)
       inc(indent)
 
-    when defined(nimfix):
-      template doContinue(): untyped =
-        buf[pos] == '#' and (col == indent or lastBackslash > 0)
-    else:
-      template doContinue(): untyped =
-        buf[pos] == '#' and buf[pos+1] == '#'
-    if doContinue():
+    if buf[pos] == '#' and buf[pos+1] == '#':
       tok.literal.add "\n"
-      when defined(nimfix): col = indent
-      else:
-        inc(pos, 2)
-        var c = toStrip
-        while buf[pos] == ' ' and c > 0:
-          inc pos
-          dec c
+      inc(pos, 2)
+      var c = toStrip
+      while buf[pos] == ' ' and c > 0:
+        inc pos
+        dec c
       inc tok.iNumber
     else:
       if buf[pos] > ' ':
@@ -915,7 +897,7 @@ proc skip(L: var TLexer, tok: var TToken) =
       inc(pos)
       inc(tok.strongSpaceA)
     of '\t':
-      lexMessagePos(L, errTabulatorsAreNotAllowed, pos)
+      if not L.allowTabs: lexMessagePos(L, errTabulatorsAreNotAllowed, pos)
       inc(pos)
     of CR, LF:
       pos = handleCRLF(L, pos)
@@ -932,27 +914,19 @@ proc skip(L: var TLexer, tok: var TToken) =
         else:
           break
       tok.strongSpaceA = 0
-      when defined(nimfix):
-        template doBreak(): untyped = buf[pos] > ' '
-      else:
-        template doBreak(): untyped =
-          buf[pos] > ' ' and (buf[pos] != '#' or buf[pos+1] == '#')
-      if doBreak():
+      if buf[pos] > ' ' and (buf[pos] != '#' or buf[pos+1] == '#'):
         tok.indent = indent
         L.currLineIndent = indent
         break
     of '#':
-      when defined(nimfix):
-        break
+      # do not skip documentation comment:
+      if buf[pos+1] == '#': break
+      if buf[pos+1] == '[':
+        skipMultiLineComment(L, tok, pos+2, false)
+        pos = L.bufpos
+        buf = L.buf
       else:
-        # do not skip documentation comment:
-        if buf[pos+1] == '#': break
-        if buf[pos+1] == '[':
-          skipMultiLineComment(L, tok, pos+2, false)
-          pos = L.bufpos
-          buf = L.buf
-        else:
-          while buf[pos] notin {CR, LF, nimlexbase.EndOfFile}: inc(pos)
+        while buf[pos] notin {CR, LF, nimlexbase.EndOfFile}: inc(pos)
     else:
       break                   # EndOfFile also leaves the loop
   L.bufpos = pos
@@ -1051,7 +1025,7 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) =
       if L.buf[L.bufpos] notin SymChars+{'_'} and not
           isMagicIdentSeparatorRune(L.buf, L.bufpos):
         tok.tokType = tkSymbol
-        tok.ident = getIdent("_")
+        tok.ident = L.cache.getIdent("_")
       else:
         tok.literal = $c
         tok.tokType = tkInvalid
@@ -1084,5 +1058,3 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) =
         tok.tokType = tkInvalid
         lexMessage(L, errInvalidToken, c & " (\\" & $(ord(c)) & ')')
         inc(L.bufpos)
-
-dummyIdent = getIdent("")
diff --git a/compiler/lookups.nim b/compiler/lookups.nim
index df19a6afb..fe159011c 100644
--- a/compiler/lookups.nim
+++ b/compiler/lookups.nim
@@ -242,6 +242,15 @@ proc errorUseQualifier*(c: PContext; info: TLineInfo; s: PSym) =
     inc i
   localError(info, errGenerated, err)
 
+proc errorUndeclaredIdentifier*(c: PContext; info: TLineInfo; name: string) =
+  var err = "undeclared identifier: '" & name & "'"
+  if c.recursiveDep.len > 0:
+    err.add "\nThis might be caused by a recursive module dependency: "
+    err.add c.recursiveDep
+    # prevent excessive errors for 'nim check'
+    c.recursiveDep = nil
+  localError(info, errGenerated, err)
+
 proc lookUp*(c: PContext, n: PNode): PSym =
   # Looks up a symbol. Generates an error in case of nil.
   case n.kind
@@ -249,7 +258,7 @@ proc lookUp*(c: PContext, n: PNode): PSym =
     result = searchInScopes(c, n.ident).skipAlias(n)
     if result == nil:
       fixSpelling(n, n.ident, searchInScopes)
-      localError(n.info, errUndeclaredIdentifier, n.ident.s)
+      errorUndeclaredIdentifier(c, n.info, n.ident.s)
       result = errorSym(c, n)
   of nkSym:
     result = n.sym
@@ -258,7 +267,7 @@ proc lookUp*(c: PContext, n: PNode): PSym =
     result = searchInScopes(c, ident).skipAlias(n)
     if result == nil:
       fixSpelling(n, ident, searchInScopes)
-      localError(n.info, errUndeclaredIdentifier, ident.s)
+      errorUndeclaredIdentifier(c, n.info, ident.s)
       result = errorSym(c, n)
   else:
     internalError(n.info, "lookUp")
@@ -282,7 +291,7 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
       result = searchInScopes(c, ident, allExceptModule).skipAlias(n)
     if result == nil and checkUndeclared in flags:
       fixSpelling(n, ident, searchInScopes)
-      localError(n.info, errUndeclaredIdentifier, ident.s)
+      errorUndeclaredIdentifier(c, n.info, ident.s)
       result = errorSym(c, n)
     elif checkAmbiguity in flags and result != nil and
         contains(c.ambiguousSymbols, result.id):
@@ -307,7 +316,7 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
           result = strTableGet(m.tab, ident).skipAlias(n)
         if result == nil and checkUndeclared in flags:
           fixSpelling(n.sons[1], ident, searchInScopes)
-          localError(n.sons[1].info, errUndeclaredIdentifier, ident.s)
+          errorUndeclaredIdentifier(c, n.sons[1].info, ident.s)
           result = errorSym(c, n.sons[1])
       elif n.sons[1].kind == nkSym:
         result = n.sons[1].sym
diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim
index 9db4383f6..6a8eccb83 100644
--- a/compiler/lowerings.nim
+++ b/compiler/lowerings.nim
@@ -284,7 +284,7 @@ proc addLocalVar(varSection, varInit: PNode; owner: PSym; typ: PType;
       varInit.add newFastAsgnStmt(newSymNode(result), v)
     else:
       let deepCopyCall = newNodeI(nkCall, varInit.info, 3)
-      deepCopyCall.sons[0] = newSymNode(createMagic("deepCopy", mDeepCopy))
+      deepCopyCall.sons[0] = newSymNode(getSysMagic("deepCopy", mDeepCopy))
       deepCopyCall.sons[1] = newSymNode(result)
       deepCopyCall.sons[2] = v
       varInit.add deepCopyCall
@@ -356,7 +356,7 @@ proc createWrapperProc(f: PNode; threadParam, argsParam: PSym;
       if fk == fvGC: "data" else: "blob", fv.info), call)
     if fk == fvGC:
       let incRefCall = newNodeI(nkCall, fv.info, 2)
-      incRefCall.sons[0] = newSymNode(createMagic("GCref", mGCref))
+      incRefCall.sons[0] = newSymNode(getSysMagic("GCref", mGCref))
       incRefCall.sons[1] = indirectAccess(threadLocalProm.newSymNode,
                                           "data", fv.info)
       body.add incRefCall
@@ -446,7 +446,7 @@ proc genHigh*(n: PNode): PNode =
   else:
     result = newNodeI(nkCall, n.info, 2)
     result.typ = getSysType(tyInt)
-    result.sons[0] = newSymNode(createMagic("high", mHigh))
+    result.sons[0] = newSymNode(getSysMagic("high", mHigh))
     result.sons[1] = n
 
 proc setupArgsForParallelism(n: PNode; objType: PType; scratchObj: PSym;
diff --git a/compiler/magicsys.nim b/compiler/magicsys.nim
index 13b365d04..6a9d69082 100644
--- a/compiler/magicsys.nim
+++ b/compiler/magicsys.nim
@@ -38,6 +38,14 @@ proc getSysSym*(name: string): PSym =
   if result.kind == skStub: loadStub(result)
   if result.kind == skAlias: result = result.owner
 
+proc createMagic*(name: string, m: TMagic): PSym =
+  result = newSym(skProc, getIdent(name), nil, unknownLineInfo())
+  result.magic = m
+
+let
+  opNot* = createMagic("not", mNot)
+  opContains* = createMagic("contains", mInSet)
+
 proc getSysMagic*(name: string, m: TMagic): PSym =
   var ti: TIdentIter
   let id = getIdent(name)
@@ -46,7 +54,7 @@ proc getSysMagic*(name: string, m: TMagic): PSym =
     if r.kind == skStub: loadStub(r)
     if r.magic == m:
       # prefer the tyInt variant:
-      if r.typ.sons[0].kind == tyInt: return r
+      if r.typ.sons[0] != nil and r.typ.sons[0].kind == tyInt: return r
       result = r
     r = nextIdentIter(ti, systemModule.tab)
   if result != nil: return result
diff --git a/compiler/main.nim b/compiler/main.nim
index 0db66b53e..2118078be 100644
--- a/compiler/main.nim
+++ b/compiler/main.nim
@@ -15,7 +15,8 @@ import
   wordrecg, sem, semdata, idents, passes, docgen, extccomp,
   cgen, jsgen, json, nversion,
   platform, nimconf, importer, passaux, depends, vm, vmdef, types, idgen,
-  docgen2, service, parser, modules, ccgutils, sigmatch, ropes, lists
+  docgen2, service, parser, modules, ccgutils, sigmatch, ropes, lists,
+  modulegraphs
 
 from magicsys import systemModule, resetSysTypes
 
@@ -30,80 +31,44 @@ proc semanticPasses =
   registerPass verbosePass
   registerPass semPass
 
-proc commandGenDepend =
+proc commandGenDepend(graph: ModuleGraph; cache: IdentCache) =
   semanticPasses()
   registerPass(gendependPass)
   registerPass(cleanupPass)
-  compileProject()
+  compileProject(graph, cache)
   generateDot(gProjectFull)
   execExternalProgram("dot -Tpng -o" & changeFileExt(gProjectFull, "png") &
       ' ' & changeFileExt(gProjectFull, "dot"))
 
-proc commandCheck =
+proc commandCheck(graph: ModuleGraph; cache: IdentCache) =
   msgs.gErrorMax = high(int)  # do not stop after first error
   defineSymbol("nimcheck")
   semanticPasses()            # use an empty backend for semantic checking only
   rodPass()
-  compileProject()
+  compileProject(graph, cache)
 
-proc commandDoc2(json: bool) =
+proc commandDoc2(graph: ModuleGraph; cache: IdentCache; json: bool) =
   msgs.gErrorMax = high(int)  # do not stop after first error
   semanticPasses()
   if json: registerPass(docgen2JsonPass)
   else: registerPass(docgen2Pass)
   #registerPass(cleanupPass())
-  compileProject()
+  compileProject(graph, cache)
   finishDoc2Pass(gProjectName)
 
-proc commandCompileToC =
+proc commandCompileToC(graph: ModuleGraph; cache: IdentCache) =
   extccomp.initVars()
   semanticPasses()
   registerPass(cgenPass)
   rodPass()
   #registerPass(cleanupPass())
 
-  compileProject()
+  compileProject(graph, cache)
   cgenWriteModules()
   if gCmd != cmdRun:
     extccomp.callCCompiler(changeFileExt(gProjectFull, ""))
 
-  if isServing:
-    # caas will keep track only of the compilation commands
-    lastCaasCmd = curCaasCmd
-    resetCgenModules()
-    for i in 0 .. <gMemCacheData.len:
-      gMemCacheData[i].hashStatus = hashCached
-      gMemCacheData[i].needsRecompile = Maybe
-
-      # XXX: clean these global vars
-      # ccgstmts.gBreakpoints
-      # ccgthreadvars.nimtv
-      # ccgthreadvars.nimtVDeps
-      # ccgthreadvars.nimtvDeclared
-      # cgendata
-      # cgmeth?
-      # condsyms?
-      # depends?
-      # lexer.gLinesCompiled
-      # msgs - error counts
-      # magicsys, when system.nim changes
-      # rodread.rodcompilerProcs
-      # rodread.gTypeTable
-      # rodread.gMods
-
-      # !! ropes.cache
-      # semthreads.computed?
-      #
-      # suggest.usageSym
-      #
-      # XXX: can we run out of IDs?
-      # XXX: detect config reloading (implement as error/require restart)
-      # XXX: options are appended (they will accumulate over time)
-    resetCompilationLists()
-    ccgutils.resetCaches()
-    GC_fullCollect()
-
-proc commandCompileToJS =
+proc commandCompileToJS(graph: ModuleGraph; cache: IdentCache) =
   #incl(gGlobalOptions, optSafeCode)
   setTarget(osJS, cpuJS)
   #initDefines()
@@ -113,9 +78,9 @@ proc commandCompileToJS =
   if gCmd == cmdCompileToPHP: defineSymbol("nimphp")
   semanticPasses()
   registerPass(JSgenPass)
-  compileProject()
+  compileProject(graph, cache)
 
-proc interactivePasses =
+proc interactivePasses(graph: ModuleGraph; cache: IdentCache) =
   #incl(gGlobalOptions, optSafeCode)
   #setTarget(osNimrodVM, cpuNimrodVM)
   initDefines()
@@ -125,30 +90,30 @@ proc interactivePasses =
   registerPass(semPass)
   registerPass(evalPass)
 
-proc commandInteractive =
+proc commandInteractive(graph: ModuleGraph; cache: IdentCache) =
   msgs.gErrorMax = high(int)  # do not stop after first error
-  interactivePasses()
-  compileSystemModule()
+  interactivePasses(graph, cache)
+  compileSystemModule(graph, cache)
   if commandArgs.len > 0:
-    discard compileModule(fileInfoIdx(gProjectFull), {})
+    discard graph.compileModule(fileInfoIdx(gProjectFull), cache, {})
   else:
-    var m = makeStdinModule()
+    var m = graph.makeStdinModule()
     incl(m.flags, sfMainModule)
-    processModule(m, llStreamOpenStdIn(), nil)
+    processModule(graph, m, llStreamOpenStdIn(), nil, cache)
 
 const evalPasses = [verbosePass, semPass, evalPass]
 
-proc evalNim(nodes: PNode, module: PSym) =
-  carryPasses(nodes, module, evalPasses)
+proc evalNim(graph: ModuleGraph; nodes: PNode, module: PSym; cache: IdentCache) =
+  carryPasses(graph, nodes, module, cache, evalPasses)
 
-proc commandEval(exp: string) =
+proc commandEval(graph: ModuleGraph; cache: IdentCache; exp: string) =
   if systemModule == nil:
-    interactivePasses()
-    compileSystemModule()
-  var echoExp = "echo \"eval\\t\", " & "repr(" & exp & ")"
-  evalNim(echoExp.parseString, makeStdinModule())
+    interactivePasses(graph, cache)
+    compileSystemModule(graph, cache)
+  let echoExp = "echo \"eval\\t\", " & "repr(" & exp & ")"
+  evalNim(graph, echoExp.parseString(cache), makeStdinModule(graph), cache)
 
-proc commandScan =
+proc commandScan(cache: IdentCache) =
   var f = addFileExt(mainCommandArg(), NimExt)
   var stream = llStreamOpen(f, fmRead)
   if stream != nil:
@@ -156,7 +121,7 @@ proc commandScan =
       L: TLexer
       tok: TToken
     initToken(tok)
-    openLexer(L, f, stream)
+    openLexer(L, f, stream, cache)
     while true:
       rawGetTok(L, tok)
       printTok(tok)
@@ -165,77 +130,11 @@ proc commandScan =
   else:
     rawMessage(errCannotOpenFile, f)
 
-proc commandSuggest =
-  if isServing:
-    # XXX: hacky work-around ahead
-    # Currently, it's possible to issue a idetools command, before
-    # issuing the first compile command. This will leave the compiler
-    # cache in a state where "no recompilation is necessary", but the
-    # cgen pass was never executed at all.
-    commandCompileToC()
-    let gDirtyBufferIdx = gTrackPos.fileIndex
-    discard compileModule(gDirtyBufferIdx, {sfDirty})
-    resetModule(gDirtyBufferIdx)
-  else:
-    msgs.gErrorMax = high(int)  # do not stop after first error
-    semanticPasses()
-    rodPass()
-    # XXX: this handles the case when the dirty buffer is the main file,
-    # but doesn't handle the case when it's imported module
-    #var projFile = if gProjectMainIdx == gDirtyOriginalIdx: gDirtyBufferIdx
-    #               else: gProjectMainIdx
-    compileProject() #(projFile)
-
-proc resetMemory =
-  resetCompilationLists()
-  ccgutils.resetCaches()
-  resetAllModules()
-  resetRopeCache()
-  resetSysTypes()
-  gOwners = @[]
-  for i in low(buckets)..high(buckets):
-    buckets[i] = nil
-  idAnon = nil
-
-  # XXX: clean these global vars
-  # ccgstmts.gBreakpoints
-  # ccgthreadvars.nimtv
-  # ccgthreadvars.nimtVDeps
-  # ccgthreadvars.nimtvDeclared
-  # cgendata
-  # cgmeth?
-  # condsyms?
-  # depends?
-  # lexer.gLinesCompiled
-  # msgs - error counts
-  # magicsys, when system.nim changes
-  # rodread.rodcompilerProcs
-  # rodread.gTypeTable
-  # rodread.gMods
-
-  # !! ropes.cache
-  #
-  # suggest.usageSym
-  #
-  # XXX: can we run out of IDs?
-  # XXX: detect config reloading (implement as error/require restart)
-  # XXX: options are appended (they will accumulate over time)
-  # vis = visimpl
-  when compileOption("gc", "v2"):
-    gcDebugging = true
-    echo "COLLECT 1"
-    GC_fullCollect()
-    echo "COLLECT 2"
-    GC_fullCollect()
-    echo "COLLECT 3"
-    GC_fullCollect()
-    echo GC_getStatistics()
-
 const
   SimulateCaasMemReset = false
   PrintRopeCacheStats = false
 
-proc mainCommand* =
+proc mainCommand*(graph: ModuleGraph; cache: IdentCache) =
   when SimulateCaasMemReset:
     gGlobalOptions.incl(optCaasEnabled)
 
@@ -251,66 +150,66 @@ proc mainCommand* =
   of "c", "cc", "compile", "compiletoc":
     # compile means compileToC currently
     gCmd = cmdCompileToC
-    commandCompileToC()
+    commandCompileToC(graph, cache)
   of "cpp", "compiletocpp":
     gCmd = cmdCompileToCpp
     defineSymbol("cpp")
-    commandCompileToC()
+    commandCompileToC(graph, cache)
   of "objc", "compiletooc":
     gCmd = cmdCompileToOC
     defineSymbol("objc")
-    commandCompileToC()
+    commandCompileToC(graph, cache)
   of "run":
     gCmd = cmdRun
     when hasTinyCBackend:
       extccomp.setCC("tcc")
-      commandCompileToC()
+      commandCompileToC(graph, cache)
     else:
       rawMessage(errInvalidCommandX, command)
   of "js", "compiletojs":
     gCmd = cmdCompileToJS
-    commandCompileToJS()
+    commandCompileToJS(graph, cache)
   of "php":
     gCmd = cmdCompileToPHP
-    commandCompileToJS()
+    commandCompileToJS(graph, cache)
   of "doc":
     wantMainModule()
     gCmd = cmdDoc
-    loadConfigs(DocConfig)
+    loadConfigs(DocConfig, cache)
     commandDoc()
   of "doc2":
     gCmd = cmdDoc
-    loadConfigs(DocConfig)
+    loadConfigs(DocConfig, cache)
     defineSymbol("nimdoc")
-    commandDoc2(false)
+    commandDoc2(graph, cache, false)
   of "rst2html":
     gCmd = cmdRst2html
-    loadConfigs(DocConfig)
+    loadConfigs(DocConfig, cache)
     commandRst2Html()
   of "rst2tex":
     gCmd = cmdRst2tex
-    loadConfigs(DocTexConfig)
+    loadConfigs(DocTexConfig, cache)
     commandRst2TeX()
   of "jsondoc":
     wantMainModule()
     gCmd = cmdDoc
-    loadConfigs(DocConfig)
+    loadConfigs(DocConfig, cache)
     wantMainModule()
     defineSymbol("nimdoc")
     commandJson()
   of "jsondoc2":
     gCmd = cmdDoc
-    loadConfigs(DocConfig)
+    loadConfigs(DocConfig, cache)
     wantMainModule()
     defineSymbol("nimdoc")
-    commandDoc2(true)
+    commandDoc2(graph, cache, true)
   of "buildindex":
     gCmd = cmdDoc
-    loadConfigs(DocConfig)
+    loadConfigs(DocConfig, cache)
     commandBuildIndex()
   of "gendepend":
     gCmd = cmdGenDepend
-    commandGenDepend()
+    commandGenDepend(graph, cache)
   of "dump":
     gCmd = cmdDump
     if getConfigVar("dump.format") == "json":
@@ -339,35 +238,21 @@ proc mainCommand* =
       for it in iterSearchPath(searchPaths): msgWriteln(it)
   of "check":
     gCmd = cmdCheck
-    commandCheck()
+    commandCheck(graph, cache)
   of "parse":
     gCmd = cmdParse
     wantMainModule()
-    discard parseFile(gProjectMainIdx)
+    discard parseFile(gProjectMainIdx, cache)
   of "scan":
     gCmd = cmdScan
     wantMainModule()
-    commandScan()
-    msgWriteln("Beware: Indentation tokens depend on the parser\'s state!")
+    commandScan(cache)
+    msgWriteln("Beware: Indentation tokens depend on the parser's state!")
   of "secret":
     gCmd = cmdInteractive
-    commandInteractive()
+    commandInteractive(graph, cache)
   of "e":
-    # XXX: temporary command for easier testing
-    commandEval(mainCommandArg())
-  of "reset":
-    resetMemory()
-  of "idetools":
-    gCmd = cmdIdeTools
-    if gEvalExpr != "":
-      commandEval(gEvalExpr)
-    else:
-      commandSuggest()
-  of "serve":
-    isServing = true
-    gGlobalOptions.incl(optCaasEnabled)
-    msgs.gErrorMax = high(int)  # do not stop after first error
-    serve(mainCommand)
+    commandEval(graph, cache, mainCommandArg())
   of "nop", "help":
     # prevent the "success" message:
     gCmd = cmdDump
@@ -394,3 +279,5 @@ proc mainCommand* =
     resetMemory()
 
   resetAttributes()
+
+proc mainCommand*() = mainCommand(newModuleGraph(), newIdentCache())
diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim
new file mode 100644
index 000000000..38fd4f89f
--- /dev/null
+++ b/compiler/modulegraphs.nim
@@ -0,0 +1,112 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2016 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module implements the module graph data structure. The module graph
+## represents a complete Nim project. Single modules can either be kept in RAM
+## or stored in a ROD file. The ROD file mechanism is not yet integrated here.
+##
+## The caching of modules is critical for 'nimsuggest' and is tricky to get
+## right. If module E is being edited, we need autocompletion (and type
+## checking) for E but we don't want to recompile depending
+## modules right away for faster turnaround times. Instead we mark the module's
+## dependencies as 'dirty'. Let D be a dependency of E. If D is dirty, we
+## need to recompile it and all of its dependencies that are marked as 'dirty'.
+## 'nimsuggest sug' actually is invoked for the file being edited so we know
+## its content changed and there is no need to compute any checksums.
+## Instead of a recursive algorithm, we use an iterative algorithm:
+##
+## - If a module gets recompiled, its dependencies need to be updated.
+## - Its dependent module stays the same.
+##
+
+import ast, intsets, tables
+
+type
+  ModuleGraph* = ref object
+    modules*: seq[PSym]  ## indexed by int32 fileIdx
+    packageSyms*: TStrTable
+    deps*: IntSet # the dependency graph or potentially its transitive closure.
+    suggestMode*: bool # whether we are in nimsuggest mode or not.
+    invalidTransitiveClosure: bool
+    inclToMod*: Table[int32, int32] # mapping of include file to the
+                                    # first module that included it
+    importStack*: seq[int32]  # The current import stack. Used for detecting recursive
+                              # module dependencies.
+
+{.this: g.}
+
+proc newModuleGraph*(): ModuleGraph =
+  result = ModuleGraph()
+  initStrTable(result.packageSyms)
+  result.deps = initIntSet()
+  result.modules = @[]
+  result.importStack = @[]
+  result.inclToMod = initTable[int32, int32]()
+
+proc resetAllModules*(g: ModuleGraph) =
+  initStrTable(packageSyms)
+  deps = initIntSet()
+  modules = @[]
+  importStack = @[]
+  inclToMod = initTable[int32, int32]()
+
+proc getModule*(g: ModuleGraph; fileIdx: int32): PSym =
+  if fileIdx >= 0 and fileIdx < modules.len:
+    result = modules[fileIdx]
+
+proc dependsOn(a, b: int): int {.inline.} = (a shl 15) + b
+
+proc addDep*(g: ModuleGraph; m: PSym, dep: int32) =
+  if suggestMode:
+    deps.incl m.position.dependsOn(dep)
+    # we compute the transitive closure later when quering the graph lazily.
+    # this improve efficiency quite a lot:
+    invalidTransitiveClosure = true
+
+proc addIncludeDep*(g: ModuleGraph; module, includeFile: int32) =
+  discard hasKeyOrPut(inclToMod, includeFile, module)
+
+proc parentModule*(g: ModuleGraph; fileIdx: int32): int32 =
+  ## returns 'fileIdx' if the file belonging to this index is
+  ## directly used as a module or else the module that first
+  ## references this include file.
+  if fileIdx >= 0 and fileIdx < modules.len and modules[fileIdx] != nil:
+    result = fileIdx
+  else:
+    result = inclToMod.getOrDefault(fileIdx)
+
+proc transitiveClosure(g: var IntSet; n: int) =
+  # warshall's algorithm
+  for k in 0..<n:
+    for i in 0..<n:
+      for j in 0..<n:
+        if i != j and not g.contains(i.dependsOn(j)):
+          if g.contains(i.dependsOn(k)) and g.contains(k.dependsOn(j)):
+            g.incl i.dependsOn(j)
+
+proc markDirty*(g: ModuleGraph; fileIdx: int32) =
+  let m = getModule fileIdx
+  if m != nil: incl m.flags, sfDirty
+
+proc markClientsDirty*(g: ModuleGraph; fileIdx: int32) =
+  # we need to mark its dependent modules D as dirty right away because after
+  # nimsuggest is done with this module, the module's dirty flag will be
+  # cleared but D still needs to be remembered as 'dirty'.
+  if invalidTransitiveClosure:
+    invalidTransitiveClosure = false
+    transitiveClosure(deps, modules.len)
+
+  # every module that *depends* on this file is also dirty:
+  for i in 0i32..<modules.len.int32:
+    let m = modules[i]
+    if m != nil and deps.contains(i.dependsOn(fileIdx)):
+      incl m.flags, sfDirty
+
+proc isDirty*(g: ModuleGraph; m: PSym): bool =
+  result = suggestMode and sfDirty in m.flags
diff --git a/compiler/modules.nim b/compiler/modules.nim
index 711fb6aa4..3451d85ec 100644
--- a/compiler/modules.nim
+++ b/compiler/modules.nim
@@ -7,130 +7,121 @@
 #    distribution, for details about the copyright.
 #
 
-## implements the module handling
+## Implements the module handling, including the caching of modules.
 
 import
   ast, astalgo, magicsys, securehash, rodread, msgs, cgendata, sigmatch, options,
-  idents, os, lexer, idgen, passes, syntaxes, llstream
-
-type
-  TNeedRecompile* = enum Maybe, No, Yes, Probing, Recompiled
-  THashStatus* = enum hashNotTaken, hashCached, hashHasChanged, hashNotChanged
-
-  TModuleInMemory* = object
-    compiledAt*: float
-    hash*: SecureHash
-    deps*: seq[int32] ## XXX: slurped files are currently not tracked
-    needsRecompile*: TNeedRecompile
-    hashStatus*: THashStatus
-
-var
-  gCompiledModules: seq[PSym] = @[]
-  gMemCacheData*: seq[TModuleInMemory] = @[]
-    ## XXX: we should implement recycling of file IDs
-    ## if the user keeps renaming modules, the file IDs will keep growing
-  gFuzzyGraphChecking*: bool # nimsuggest uses this. XXX figure out why.
-  packageSyms: TStrTable
-
-initStrTable(packageSyms)
-
-proc getModule*(fileIdx: int32): PSym =
-  if fileIdx >= 0 and fileIdx < gCompiledModules.len:
-    result = gCompiledModules[fileIdx]
-
-proc hashChanged(fileIdx: int32): bool =
-  internalAssert fileIdx >= 0 and fileIdx < gMemCacheData.len
-
-  template updateStatus =
-    gMemCacheData[fileIdx].hashStatus = if result: hashHasChanged
-                                       else: hashNotChanged
-    # echo "TESTING Hash: ", fileIdx.toFilename, " ", result
-
-  case gMemCacheData[fileIdx].hashStatus
-  of hashHasChanged:
-    result = true
-  of hashNotChanged:
-    result = false
-  of hashCached:
-    let newHash = secureHashFile(fileIdx.toFullPath)
-    result = newHash != gMemCacheData[fileIdx].hash
-    gMemCacheData[fileIdx].hash = newHash
-    updateStatus()
-  of hashNotTaken:
-    gMemCacheData[fileIdx].hash = secureHashFile(fileIdx.toFullPath)
-    result = true
-    updateStatus()
-
-proc doHash(fileIdx: int32) =
-  if gMemCacheData[fileIdx].hashStatus == hashNotTaken:
-    # echo "FIRST Hash: ", fileIdx.ToFilename
-    gMemCacheData[fileIdx].hash = secureHashFile(fileIdx.toFullPath)
-
-proc addDep(x: PSym, dep: int32) =
-  growCache gMemCacheData, dep
-  gMemCacheData[x.position].deps.safeAdd(dep)
-
-proc resetModule*(fileIdx: int32) =
-  # echo "HARD RESETTING ", fileIdx.toFilename
-  if fileIdx <% gMemCacheData.len:
-    gMemCacheData[fileIdx].needsRecompile = Yes
-  if fileIdx <% gCompiledModules.len:
-    gCompiledModules[fileIdx] = nil
-  if fileIdx <% cgendata.gModules.len:
-    cgendata.gModules[fileIdx] = nil
-
-proc resetModule*(module: PSym) =
-  let conflict = getModule(module.position.int32)
-  if conflict == nil: return
-  doAssert conflict == module
-  resetModule(module.position.int32)
-  initStrTable(module.tab)
-
-proc resetAllModules* =
-  for i in 0..gCompiledModules.high:
-    if gCompiledModules[i] != nil:
-      resetModule(i.int32)
-  resetPackageCache()
-  initStrTable(packageSyms)
-  # for m in cgenModules(): echo "CGEN MODULE FOUND"
-
-proc resetAllModulesHard* =
-  resetPackageCache()
-  gCompiledModules.setLen 0
-  gMemCacheData.setLen 0
-  magicsys.resetSysTypes()
-  initStrTable(packageSyms)
-  # XXX
-  #gOwners = @[]
-
-proc checkDepMem(fileIdx: int32): TNeedRecompile =
-  template markDirty =
-    resetModule(fileIdx)
-    return Yes
-
-  if gFuzzyGraphChecking:
-    if gMemCacheData[fileIdx].needsRecompile != Maybe:
-      return gMemCacheData[fileIdx].needsRecompile
-  else:
-    # cycle detection: We claim that a cycle does no harm.
-    if gMemCacheData[fileIdx].needsRecompile == Probing:
-      return No
+  idents, os, lexer, idgen, passes, syntaxes, llstream, modulegraphs
+
+when false:
+  type
+    TNeedRecompile* = enum Maybe, No, Yes, Probing, Recompiled
+    THashStatus* = enum hashNotTaken, hashCached, hashHasChanged, hashNotChanged
+
+    TModuleInMemory* = object
+      hash*: SecureHash
+      deps*: seq[int32] ## XXX: slurped files are currently not tracked
+
+      needsRecompile*: TNeedRecompile
+      hashStatus*: THashStatus
+
+  var
+    gCompiledModules: seq[PSym] = @[]
+    gMemCacheData*: seq[TModuleInMemory] = @[]
+      ## XXX: we should implement recycling of file IDs
+      ## if the user keeps renaming modules, the file IDs will keep growing
+    gFuzzyGraphChecking*: bool # nimsuggest uses this. XXX figure out why.
+
+  proc hashChanged(fileIdx: int32): bool =
+    internalAssert fileIdx >= 0 and fileIdx < gMemCacheData.len
+
+    template updateStatus =
+      gMemCacheData[fileIdx].hashStatus = if result: hashHasChanged
+                                         else: hashNotChanged
+      # echo "TESTING Hash: ", fileIdx.toFilename, " ", result
+
+    case gMemCacheData[fileIdx].hashStatus
+    of hashHasChanged:
+      result = true
+    of hashNotChanged:
+      result = false
+    of hashCached:
+      let newHash = secureHashFile(fileIdx.toFullPath)
+      result = newHash != gMemCacheData[fileIdx].hash
+      gMemCacheData[fileIdx].hash = newHash
+      updateStatus()
+    of hashNotTaken:
+      gMemCacheData[fileIdx].hash = secureHashFile(fileIdx.toFullPath)
+      result = true
+      updateStatus()
+
+  proc doHash(fileIdx: int32) =
+    if gMemCacheData[fileIdx].hashStatus == hashNotTaken:
+      # echo "FIRST Hash: ", fileIdx.ToFilename
+      gMemCacheData[fileIdx].hash = secureHashFile(fileIdx.toFullPath)
+
+  proc resetModule*(fileIdx: int32) =
+    # echo "HARD RESETTING ", fileIdx.toFilename
+    if fileIdx <% gMemCacheData.len:
+      gMemCacheData[fileIdx].needsRecompile = Yes
+    if fileIdx <% gCompiledModules.len:
+      gCompiledModules[fileIdx] = nil
+    if fileIdx <% cgendata.gModules.len:
+      cgendata.gModules[fileIdx] = nil
+
+  proc resetModule*(module: PSym) =
+    let conflict = getModule(module.position.int32)
+    if conflict == nil: return
+    doAssert conflict == module
+    resetModule(module.position.int32)
+    initStrTable(module.tab)
+
+  proc resetAllModules* =
+    for i in 0..gCompiledModules.high:
+      if gCompiledModules[i] != nil:
+        resetModule(i.int32)
+    resetPackageCache()
+    # for m in cgenModules(): echo "CGEN MODULE FOUND"
+
+  proc resetAllModulesHard* =
+    resetPackageCache()
+    gCompiledModules.setLen 0
+    gMemCacheData.setLen 0
+    magicsys.resetSysTypes()
+    # XXX
+    #gOwners = @[]
+
+  proc checkDepMem(fileIdx: int32): TNeedRecompile =
+    template markDirty =
+      resetModule(fileIdx)
+      return Yes
+
+    if gFuzzyGraphChecking:
+      if gMemCacheData[fileIdx].needsRecompile != Maybe:
+        return gMemCacheData[fileIdx].needsRecompile
+    else:
+      # cycle detection: We claim that a cycle does no harm.
+      if gMemCacheData[fileIdx].needsRecompile == Probing:
+        return No
+
+    if optForceFullMake in gGlobalOptions or hashChanged(fileIdx):
+      markDirty()
 
-  if optForceFullMake in gGlobalOptions or hashChanged(fileIdx):
-    markDirty()
+    if gMemCacheData[fileIdx].deps != nil:
+      gMemCacheData[fileIdx].needsRecompile = Probing
+      for dep in gMemCacheData[fileIdx].deps:
+        let d = checkDepMem(dep)
+        if d in {Yes, Recompiled}:
+          # echo fileIdx.toFilename, " depends on ", dep.toFilename, " ", d
+          markDirty()
 
-  if gMemCacheData[fileIdx].deps != nil:
-    gMemCacheData[fileIdx].needsRecompile = Probing
-    for dep in gMemCacheData[fileIdx].deps:
-      let d = checkDepMem(dep)
-      if d in {Yes, Recompiled}:
-        # echo fileIdx.toFilename, " depends on ", dep.toFilename, " ", d
-        markDirty()
+    gMemCacheData[fileIdx].needsRecompile = No
+    return No
 
-  gMemCacheData[fileIdx].needsRecompile = No
-  return No
+proc resetSystemArtifacts*() =
+  magicsys.resetSysTypes()
 
-proc newModule(fileIdx: int32): PSym =
+proc newModule(graph: ModuleGraph; fileIdx: int32): PSym =
   # We cannot call ``newSym`` here, because we have to circumvent the ID
   # mechanism, which we do in order to assign each module a persistent ID.
   new(result)
@@ -143,20 +134,19 @@ proc newModule(fileIdx: int32): PSym =
 
   result.info = newLineInfo(fileIdx, 1, 1)
   let pack = getIdent(getPackageName(filename))
-  var packSym = packageSyms.strTableGet(pack)
+  var packSym = graph.packageSyms.strTableGet(pack)
   if packSym == nil:
     let pck = getPackageName(filename)
     let pck2 = if pck.len > 0: pck else: "unknown"
     packSym = newSym(skPackage, getIdent(pck2), nil, result.info)
     initStrTable(packSym.tab)
-    packageSyms.strTableAdd(packSym)
+    graph.packageSyms.strTableAdd(packSym)
 
   result.owner = packSym
   result.position = fileIdx
 
-  growCache gMemCacheData, fileIdx
-  growCache gCompiledModules, fileIdx
-  gCompiledModules[result.position] = result
+  growCache graph.modules, fileIdx
+  graph.modules[result.position] = result
 
   incl(result.flags, sfUsed)
   initStrTable(result.tab)
@@ -167,57 +157,66 @@ proc newModule(fileIdx: int32): PSym =
   # strTableIncl() for error corrections:
   discard strTableIncl(packSym.tab, result)
 
-proc compileModule*(fileIdx: int32, flags: TSymFlags): PSym =
-  result = getModule(fileIdx)
+proc compileModule*(graph: ModuleGraph; fileIdx: int32; cache: IdentCache, flags: TSymFlags): PSym =
+  result = graph.getModule(fileIdx)
   if result == nil:
-    growCache gMemCacheData, fileIdx
-    gMemCacheData[fileIdx].needsRecompile = Probing
-    result = newModule(fileIdx)
-    #var rd = handleSymbolFile(result)
+    #growCache gMemCacheData, fileIdx
+    #gMemCacheData[fileIdx].needsRecompile = Probing
+    result = newModule(graph, fileIdx)
     var rd: PRodReader
     result.flags = result.flags + flags
     if sfMainModule in result.flags:
       gMainPackageId = result.owner.id
 
     if gCmd in {cmdCompileToC, cmdCompileToCpp, cmdCheck, cmdIdeTools}:
-      rd = handleSymbolFile(result)
+      rd = handleSymbolFile(result, cache)
       if result.id < 0:
-        internalError("handleSymbolFile should have set the module\'s ID")
+        internalError("handleSymbolFile should have set the module's ID")
         return
     else:
       result.id = getID()
-    let validFile = processModule(result, if sfMainModule in flags and gProjectIsStdin: llStreamOpen(stdin) else: nil, rd)
-    if optCaasEnabled in gGlobalOptions:
-      gMemCacheData[fileIdx].compiledAt = gLastCmdTime
-      gMemCacheData[fileIdx].needsRecompile = Recompiled
-      if validFile: doHash fileIdx
-  else:
-    if checkDepMem(fileIdx) == Yes:
-      result = compileModule(fileIdx, flags)
-    else:
-      result = gCompiledModules[fileIdx]
-
-proc importModule*(s: PSym, fileIdx: int32): PSym {.procvar.} =
+    discard processModule(graph, result,
+      if sfMainModule in flags and gProjectIsStdin: stdin.llStreamOpen else: nil,
+      rd, cache)
+    #if optCaasEnabled in gGlobalOptions:
+    #  gMemCacheData[fileIdx].needsRecompile = Recompiled
+    #  if validFile: doHash fileIdx
+  elif graph.isDirty(result):
+    result.flags.excl sfDirty
+    # reset module fields:
+    initStrTable(result.tab)
+    result.ast = nil
+    discard processModule(graph, result,
+      if sfMainModule in flags and gProjectIsStdin: stdin.llStreamOpen else: nil,
+      nil, cache)
+    graph.markClientsDirty(fileIdx)
+    when false:
+      if checkDepMem(fileIdx) == Yes:
+        result = compileModule(fileIdx, cache, flags)
+      else:
+        result = gCompiledModules[fileIdx]
+
+proc importModule*(graph: ModuleGraph; s: PSym, fileIdx: int32;
+                   cache: IdentCache): PSym {.procvar.} =
   # this is called by the semantic checking phase
-  result = compileModule(fileIdx, {})
-  if optCaasEnabled in gGlobalOptions: addDep(s, fileIdx)
+  result = compileModule(graph, fileIdx, cache, {})
+  graph.addDep(s, fileIdx)
   #if sfSystemModule in result.flags:
   #  localError(result.info, errAttemptToRedefine, result.name.s)
   # restore the notes for outer module:
   gNotes = if s.owner.id == gMainPackageId: gMainPackageNotes
            else: ForeignPackageNotes
 
-proc includeModule*(s: PSym, fileIdx: int32): PNode {.procvar.} =
-  result = syntaxes.parseFile(fileIdx)
-  if optCaasEnabled in gGlobalOptions:
-    growCache gMemCacheData, fileIdx
-    addDep(s, fileIdx)
-    doHash(fileIdx)
+proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: int32;
+                    cache: IdentCache): PNode {.procvar.} =
+  result = syntaxes.parseFile(fileIdx, cache)
+  graph.addDep(s, fileIdx)
+  graph.addIncludeDep(s.position.int32, fileIdx)
 
-proc compileSystemModule* =
+proc compileSystemModule*(graph: ModuleGraph; cache: IdentCache) =
   if magicsys.systemModule == nil:
     systemFileIdx = fileInfoIdx(options.libpath/"system.nim")
-    discard compileModule(systemFileIdx, {sfSystemModule})
+    discard graph.compileModule(systemFileIdx, cache, {sfSystemModule})
 
 proc wantMainModule* =
   if gProjectFull.len == 0:
@@ -227,18 +226,20 @@ proc wantMainModule* =
 passes.gIncludeFile = includeModule
 passes.gImportModule = importModule
 
-proc compileProject*(projectFileIdx = -1'i32) =
+proc compileProject*(graph: ModuleGraph; cache: IdentCache;
+                     projectFileIdx = -1'i32) =
   wantMainModule()
   let systemFileIdx = fileInfoIdx(options.libpath / "system.nim")
   let projectFile = if projectFileIdx < 0: gProjectMainIdx else: projectFileIdx
+  graph.importStack.add projectFile
   if projectFile == systemFileIdx:
-    discard compileModule(projectFile, {sfMainModule, sfSystemModule})
+    discard graph.compileModule(projectFile, cache, {sfMainModule, sfSystemModule})
   else:
-    compileSystemModule()
-    discard compileModule(projectFile, {sfMainModule})
+    graph.compileSystemModule(cache)
+    discard graph.compileModule(projectFile, cache, {sfMainModule})
 
-proc makeModule*(filename: string): PSym =
-  result = newModule(fileInfoIdx filename)
+proc makeModule*(graph: ModuleGraph; filename: string): PSym =
+  result = graph.newModule(fileInfoIdx filename)
   result.id = getID()
 
-proc makeStdinModule*(): PSym = makeModule"stdin"
+proc makeStdinModule*(graph: ModuleGraph): PSym = graph.makeModule"stdin"
diff --git a/compiler/msgs.nim b/compiler/msgs.nim
index fd0aafccb..94b0bee00 100644
--- a/compiler/msgs.nim
+++ b/compiler/msgs.nim
@@ -35,7 +35,7 @@ type
     errNoneSpeedOrSizeExpectedButXFound, errGuiConsoleOrLibExpectedButXFound,
     errUnknownOS, errUnknownCPU, errGenOutExpectedButXFound,
     errArgsNeedRunOption, errInvalidMultipleAsgn, errColonOrEqualsExpected,
-    errExprExpected, errUndeclaredIdentifier, errUndeclaredField,
+    errExprExpected, errUndeclaredField,
     errUndeclaredRoutine, errUseQualifier,
     errTypeExpected,
     errSystemNeeds, errExecutionOfProgramFailed, errNotOverloadable,
@@ -197,7 +197,6 @@ const
     errInvalidMultipleAsgn: "multiple assignment is not allowed",
     errColonOrEqualsExpected: "\':\' or \'=\' expected, but found \'$1\'",
     errExprExpected: "expression expected, but found \'$1\'",
-    errUndeclaredIdentifier: "undeclared identifier: \'$1\'",
     errUndeclaredField: "undeclared field: \'$1\'",
     errUndeclaredRoutine: "attempting to call undeclared routine: \'$1\'",
     errUseQualifier: "ambiguous identifier: \'$1\' -- use a qualifier",
@@ -215,7 +214,7 @@ const
     errOrdinalTypeExpected: "ordinal type expected",
     errOrdinalOrFloatTypeExpected: "ordinal or float type expected",
     errOverOrUnderflow: "over- or underflow",
-    errCannotEvalXBecauseIncompletelyDefined: "cannot evalutate '$1' because type is not defined completely",
+    errCannotEvalXBecauseIncompletelyDefined: "cannot evaluate '$1' because type is not defined completely",
     errChrExpectsRange0_255: "\'chr\' expects an int in the range 0..255",
     errDynlibRequiresExportc: "\'dynlib\' requires \'exportc\'",
     errUndeclaredFieldX: "undeclared field: \'$1\'",
@@ -676,9 +675,8 @@ proc getInfoContext*(index: int): TLineInfo =
   if i >=% L: result = unknownLineInfo()
   else: result = msgContext[i]
 
-proc toFilename*(fileIdx: int32): string =
-  if fileIdx < 0: result = "???"
-  else: result = fileInfos[fileIdx].projPath
+template toFilename*(fileIdx: int32): string =
+  (if fileIdx < 0: "???" else: fileInfos[fileIdx].projPath)
 
 proc toFullPath*(fileIdx: int32): string =
   if fileIdx < 0: result = "???"
diff --git a/compiler/nim.nim b/compiler/nim.nim
index a58afd593..35afecf20 100644
--- a/compiler/nim.nim
+++ b/compiler/nim.nim
@@ -13,10 +13,15 @@ when defined(gcc) and defined(windows):
   else:
     {.link: "icons/nim_icon.o".}
 
+when defined(amd64) and defined(windows) and defined(vcc):
+  {.link: "icons/nim-amd64-windows-vcc.res".}
+when defined(i386) and defined(windows) and defined(vcc):
+  {.link: "icons/nim-i386-windows-vcc.res".}
+
 import
   commands, lexer, condsyms, options, msgs, nversion, nimconf, ropes,
   extccomp, strutils, os, osproc, platform, main, parseopt, service,
-  nodejs, scriptconfig
+  nodejs, scriptconfig, idents, modulegraphs
 
 when hasTinyCBackend:
   import tccgen
@@ -32,7 +37,7 @@ proc prependCurDir(f: string): string =
   else:
     result = f
 
-proc handleCmdLine() =
+proc handleCmdLine(cache: IdentCache) =
   if paramCount() == 0:
     writeCommandLineUsage()
   else:
@@ -41,7 +46,7 @@ proc handleCmdLine() =
     if gProjectName == "-":
       gProjectName = "stdinfile"
       gProjectFull = "stdinfile"
-      gProjectPath = getCurrentDir()
+      gProjectPath = canonicalizePath getCurrentDir()
       gProjectIsStdin = true
     elif gProjectName != "":
       try:
@@ -49,26 +54,26 @@ proc handleCmdLine() =
       except OSError:
         gProjectFull = gProjectName
       let p = splitFile(gProjectFull)
-      gProjectPath = p.dir
+      gProjectPath = canonicalizePath p.dir
       gProjectName = p.name
     else:
-      gProjectPath = getCurrentDir()
+      gProjectPath = canonicalizePath getCurrentDir()
     loadConfigs(DefaultConfig) # load all config files
     let scriptFile = gProjectFull.changeFileExt("nims")
     if fileExists(scriptFile):
-      runNimScript(scriptFile, freshDefines=false)
+      runNimScript(cache, scriptFile, freshDefines=false)
       # 'nim foo.nims' means to just run the NimScript file and do nothing more:
       if scriptFile == gProjectFull: return
     elif fileExists(gProjectPath / "config.nims"):
       # directory wide NimScript file
-      runNimScript(gProjectPath / "config.nims", freshDefines=false)
+      runNimScript(cache, gProjectPath / "config.nims", freshDefines=false)
     # now process command line arguments again, because some options in the
     # command line can overwite the config file's settings
     extccomp.initVars()
     processCmdLine(passCmd2, "")
     if options.command == "":
       rawMessage(errNoCommand, command)
-    mainCommand()
+    mainCommand(newModuleGraph(), cache)
     if optHints in gOptions and hintGCStats in gNotes: echo(GC_getStatistics())
     #echo(GC_getStatistics())
     if msgs.gErrorCounter == 0:
@@ -112,5 +117,5 @@ when compileOption("gc", "v2") or compileOption("gc", "refc"):
 condsyms.initDefines()
 
 when not defined(selftest):
-  handleCmdLine()
+  handleCmdLine(newIdentCache())
   msgQuit(int8(msgs.gErrorCounter > 0))
diff --git a/compiler/nimconf.nim b/compiler/nimconf.nim
index 496bd0123..4bf2fbc9a 100644
--- a/compiler/nimconf.nim
+++ b/compiler/nimconf.nim
@@ -156,7 +156,7 @@ proc checkSymbol(L: TLexer, tok: TToken) =
     lexMessage(L, errIdentifierExpected, tokToStr(tok))
 
 proc parseAssignment(L: var TLexer, tok: var TToken) =
-  if tok.ident.id == getIdent("-").id or tok.ident.id == getIdent("--").id:
+  if tok.ident.s == "-" or tok.ident.s == "--":
     confTok(L, tok)           # skip unnecessary prefix
   var info = getLineInfo(L, tok) # save for later in case of an error
   checkSymbol(L, tok)
@@ -179,14 +179,14 @@ proc parseAssignment(L: var TLexer, tok: var TToken) =
     if tok.tokType == tkBracketRi: confTok(L, tok)
     else: lexMessage(L, errTokenExpected, "']'")
     add(val, ']')
-  let percent = tok.ident.id == getIdent("%=").id
+  let percent = tok.ident != nil and tok.ident.s == "%="
   if tok.tokType in {tkColon, tkEquals} or percent:
     if len(val) > 0: add(val, ':')
     confTok(L, tok)           # skip ':' or '=' or '%'
     checkSymbol(L, tok)
     add(val, tokToStr(tok))
     confTok(L, tok)           # skip symbol
-    while tok.ident != nil and tok.ident.id == getIdent("&").id:
+    while tok.ident != nil and tok.ident.s == "&":
       confTok(L, tok)
       checkSymbol(L, tok)
       add(val, tokToStr(tok))
@@ -197,7 +197,7 @@ proc parseAssignment(L: var TLexer, tok: var TToken) =
   else:
     processSwitch(s, val, passPP, info)
 
-proc readConfigFile(filename: string) =
+proc readConfigFile(filename: string; cache: IdentCache) =
   var
     L: TLexer
     tok: TToken
@@ -205,7 +205,7 @@ proc readConfigFile(filename: string) =
   stream = llStreamOpen(filename, fmRead)
   if stream != nil:
     initToken(tok)
-    openLexer(L, filename, stream)
+    openLexer(L, filename, stream, cache)
     tok.tokType = tkEof       # to avoid a pointless warning
     confTok(L, tok)           # read in the first token
     while tok.tokType != tkEof: parseAssignment(L, tok)
@@ -225,22 +225,22 @@ proc getSystemConfigPath(filename: string): string =
     if not existsFile(result): result = joinPath([p, "etc", filename])
     if not existsFile(result): result = "/etc/" & filename
 
-proc loadConfigs*(cfg: string) =
+proc loadConfigs*(cfg: string; cache: IdentCache) =
   setDefaultLibpath()
 
   if optSkipConfigFile notin gGlobalOptions:
-    readConfigFile(getSystemConfigPath(cfg))
+    readConfigFile(getSystemConfigPath(cfg), cache)
 
   if optSkipUserConfigFile notin gGlobalOptions:
-    readConfigFile(getUserConfigPath(cfg))
+    readConfigFile(getUserConfigPath(cfg), cache)
 
   var pd = if gProjectPath.len > 0: gProjectPath else: getCurrentDir()
   if optSkipParentConfigFiles notin gGlobalOptions:
     for dir in parentDirs(pd, fromRoot=true, inclusive=false):
-      readConfigFile(dir / cfg)
+      readConfigFile(dir / cfg, cache)
 
   if optSkipProjConfigFile notin gGlobalOptions:
-    readConfigFile(pd / cfg)
+    readConfigFile(pd / cfg, cache)
 
     if gProjectName.len != 0:
       # new project wide config file:
@@ -251,4 +251,8 @@ proc loadConfigs*(cfg: string) =
         projectConfig = changeFileExt(gProjectFull, "nimrod.cfg")
         if fileExists(projectConfig):
           rawMessage(warnDeprecated, projectConfig)
-      readConfigFile(projectConfig)
+      readConfigFile(projectConfig, cache)
+
+proc loadConfigs*(cfg: string) =
+  # for backwards compatibility only.
+  loadConfigs(cfg, newIdentCache())
diff --git a/compiler/nimeval.nim b/compiler/nimeval.nim
index 2bddb76e7..2872bdade 100644
--- a/compiler/nimeval.nim
+++ b/compiler/nimeval.nim
@@ -8,10 +8,9 @@
 #
 
 ## exposes the Nim VM to clients.
-
 import
   ast, modules, passes, passaux, condsyms,
-  options, nimconf, lists, sem, semdata, llstream, vm
+  options, nimconf, lists, sem, semdata, llstream, vm, modulegraphs, idents
 
 proc execute*(program: string) =
   passes.gIncludeFile = includeModule
@@ -27,7 +26,9 @@ proc execute*(program: string) =
   registerPass(evalPass)
 
   appendStr(searchPaths, options.libpath)
-  compileSystemModule()
-  var m = makeStdinModule()
+  var graph = newModuleGraph()
+  var cache = newIdentCache()
+  var m = makeStdinModule(graph)
   incl(m.flags, sfMainModule)
-  processModule(m, llStreamOpen(program), nil)
+  compileSystemModule(graph,cache)
+  processModule(graph,m, llStreamOpen(program), nil, cache)
diff --git a/compiler/nimfix/nimfix.nim b/compiler/nimfix/nimfix.nim
index 39436702f..b4007cdaf 100644
--- a/compiler/nimfix/nimfix.nim
+++ b/compiler/nimfix/nimfix.nim
@@ -73,7 +73,7 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
         of "auto": gStyleCheck = StyleCheck.Auto
         else: localError(gCmdLineInfo, errOnOrOffExpected)
       of "wholeproject": gOnlyMainfile = false
-      of "besteffort": msgs.gErrorMax = high(int) # dont stop after first error
+      of "besteffort": msgs.gErrorMax = high(int) # don't stop after first error
       else:
         processSwitch(pass, p)
     of cmdArgument:
diff --git a/compiler/nimsuggest/nimsuggest.nim b/compiler/nimsuggest/nimsuggest.nim
deleted file mode 100644
index 2be368d68..000000000
--- a/compiler/nimsuggest/nimsuggest.nim
+++ /dev/null
@@ -1,12 +0,0 @@
-#
-#
-#           The Nim Compiler
-#        (c) Copyright 2015 Andreas Rumpf
-#
-#    See the file "copying.txt", included in this
-#    distribution, for details about the copyright.
-#
-
-## Nimsuggest has been moved to https://github.com/nim-lang/nimsuggest
-
-{.error: "This project has moved to the following repo: https://github.com/nim-lang/nimsuggest".}
diff --git a/compiler/nversion.nim b/compiler/nversion.nim
index d69e1e553..4d4fe6c95 100644
--- a/compiler/nversion.nim
+++ b/compiler/nversion.nim
@@ -13,5 +13,5 @@
 const
   MaxSetElements* = 1 shl 16  # (2^16) to support unicode character sets?
   VersionAsString* = system.NimVersion
-  RodFileVersion* = "1221"       # modify this if the rod-format changes!
+  RodFileVersion* = "1222"       # modify this if the rod-format changes!
 
diff --git a/compiler/options.nim b/compiler/options.nim
index 7cf707945..9edafb17a 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -34,7 +34,8 @@ type                          # please make sure we have under 32 options
     optProfiler,              # profiler turned on
     optImplicitStatic,        # optimization: implicit at compile time
                               # evaluation
-    optPatterns               # en/disable pattern matching
+    optPatterns,              # en/disable pattern matching
+    optMemTracker
 
   TOptions* = set[TOption]
   TGlobalOption* = enum       # **keep binary compatible**
@@ -231,10 +232,10 @@ proc canonicalizePath*(path: string): string =
 
 proc shortenDir*(dir: string): string =
   ## returns the interesting part of a dir
-  var prefix = getPrefixDir() & DirSep
+  var prefix = gProjectPath & DirSep
   if startsWith(dir, prefix):
     return substr(dir, len(prefix))
-  prefix = gProjectPath & DirSep
+  prefix = getPrefixDir() & DirSep
   if startsWith(dir, prefix):
     return substr(dir, len(prefix))
   result = dir
diff --git a/compiler/parampatterns.nim b/compiler/parampatterns.nim
index f8f1f355c..c51d406ac 100644
--- a/compiler/parampatterns.nim
+++ b/compiler/parampatterns.nim
@@ -230,6 +230,8 @@ proc isAssignable*(owner: PSym, n: PNode; isUnsafeAddr=false): TAssignableResult
     # builtin slice keeps lvalue-ness:
     if getMagic(n) in {mArrGet, mSlice}:
       result = isAssignable(owner, n.sons[1], isUnsafeAddr)
+    elif n.typ != nil and n.typ.kind == tyVar:
+      result = arLValue
   of nkStmtList, nkStmtListExpr:
     if n.typ != nil:
       result = isAssignable(owner, n.lastSon, isUnsafeAddr)
diff --git a/compiler/parser.nim b/compiler/parser.nim
index 40862eb63..902bf0fcb 100644
--- a/compiler/parser.nim
+++ b/compiler/parser.nim
@@ -28,15 +28,15 @@ import
   llstream, lexer, idents, strutils, ast, astalgo, msgs
 
 type
-  TParser*{.final.} = object  # A TParser object represents a module that
-                              # is being parsed
-    currInd: int              # current indentation level
+  TParser*{.final.} = object   # A TParser object represents a file that
+                               # is being parsed
+    currInd: int               # current indentation level
     firstTok, strongSpaces: bool # Has the first token been read?
                                  # Is strongSpaces on?
-    lex*: TLexer              # The lexer that is used for parsing
-    tok*: TToken              # The current token
-    inPragma: int             # Pragma level
-    inSemiStmtList: int
+    lex*: TLexer               # The lexer that is used for parsing
+    tok*: TToken               # The current token
+    inPragma*: int             # Pragma level
+    inSemiStmtList*: int
 
 proc parseAll*(p: var TParser): PNode
 proc closeParser*(p: var TParser)
@@ -73,18 +73,20 @@ proc getTok(p: var TParser) =
   rawGetTok(p.lex, p.tok)
 
 proc openParser*(p: var TParser, fileIdx: int32, inputStream: PLLStream,
+                 cache: IdentCache;
                  strongSpaces=false) =
   ## Open a parser, using the given arguments to set up its internal state.
   ##
   initToken(p.tok)
-  openLexer(p.lex, fileIdx, inputStream)
+  openLexer(p.lex, fileIdx, inputStream, cache)
   getTok(p)                   # read the first token
   p.firstTok = true
   p.strongSpaces = strongSpaces
 
 proc openParser*(p: var TParser, filename: string, inputStream: PLLStream,
+                 cache: IdentCache;
                  strongSpaces=false) =
-  openParser(p, filename.fileInfoIdx, inputStream, strongSpaces)
+  openParser(p, filename.fileInfoIdx, inputStream, cache, strongSpaces)
 
 proc closeParser(p: var TParser) =
   ## Close a parser, freeing up its resources.
@@ -320,9 +322,9 @@ proc parseSymbol(p: var TParser, allowNil = false): PNode =
                                 tkParLe..tkParDotRi}:
           accm.add(tokToStr(p.tok))
           getTok(p)
-        result.add(newIdentNodeP(getIdent(accm), p))
+        result.add(newIdentNodeP(p.lex.cache.getIdent(accm), p))
       of tokKeywordLow..tokKeywordHigh, tkSymbol, tkIntLit..tkCharLit:
-        result.add(newIdentNodeP(getIdent(tokToStr(p.tok)), p))
+        result.add(newIdentNodeP(p.lex.cache.getIdent(tokToStr(p.tok)), p))
         getTok(p)
       else:
         parMessage(p, errIdentifierExpected, p.tok)
@@ -923,7 +925,7 @@ proc parseParamList(p: var TParser, retColon = true): PNode =
     optPar(p)
     eat(p, tkParRi)
   let hasRet = if retColon: p.tok.tokType == tkColon
-               else: p.tok.tokType == tkOpr and identEq(p.tok.ident, "->")
+               else: p.tok.tokType == tkOpr and p.tok.ident.s == "->"
   if hasRet and p.tok.indent < 0:
     getTok(p)
     optInd(p, result)
@@ -2023,7 +2025,8 @@ proc parseTopLevelStmt(p: var TParser): PNode =
       if result.kind == nkEmpty: parMessage(p, errExprExpected, p.tok)
       break
 
-proc parseString*(s: string; filename: string = ""; line: int = 0;
+proc parseString*(s: string; cache: IdentCache; filename: string = "";
+                  line: int = 0;
                   errorHandler: TErrorHandler = nil): PNode =
   ## Parses a string into an AST, returning the top node.
   ## `filename` and `line`, although optional, provide info so that the
@@ -2036,7 +2039,7 @@ proc parseString*(s: string; filename: string = ""; line: int = 0;
   # XXX for now the builtin 'parseStmt/Expr' functions do not know about strong
   # spaces...
   parser.lex.errorHandler = errorHandler
-  openParser(parser, filename, stream, false)
+  openParser(parser, filename, stream, cache, false)
 
   result = parser.parseAll
   closeParser(parser)
diff --git a/compiler/passaux.nim b/compiler/passaux.nim
index d4361a671..eeaf12953 100644
--- a/compiler/passaux.nim
+++ b/compiler/passaux.nim
@@ -10,9 +10,11 @@
 ## implements some little helper passes
 
 import
-  strutils, ast, astalgo, passes, msgs, options, idgen
+  strutils, ast, astalgo, passes, idents, msgs, options, idgen
 
-proc verboseOpen(s: PSym): PPassContext =
+from modulegraphs import ModuleGraph
+
+proc verboseOpen(graph: ModuleGraph; s: PSym; cache: IdentCache): PPassContext =
   #MessageOut('compiling ' + s.name.s);
   result = nil                # we don't need a context
   rawMessage(hintProcessing, s.name.s)
diff --git a/compiler/passes.nim b/compiler/passes.nim
index b7642e3e4..3cc15147e 100644
--- a/compiler/passes.nim
+++ b/compiler/passes.nim
@@ -13,7 +13,7 @@
 import
   strutils, lists, options, ast, astalgo, llstream, msgs, platform, os,
   condsyms, idents, renderer, types, extccomp, math, magicsys, nversion,
-  nimsets, syntaxes, times, rodread, idgen
+  nimsets, syntaxes, times, rodread, idgen, modulegraphs
 
 type
   TPassContext* = object of RootObj # the pass's context
@@ -21,9 +21,9 @@ type
 
   PPassContext* = ref TPassContext
 
-  TPassOpen* = proc (module: PSym): PPassContext {.nimcall.}
+  TPassOpen* = proc (graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext {.nimcall.}
   TPassOpenCached* =
-    proc (module: PSym, rd: PRodReader): PPassContext {.nimcall.}
+    proc (graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext {.nimcall.}
   TPassClose* = proc (p: PPassContext, n: PNode): PNode {.nimcall.}
   TPassProcess* = proc (p: PPassContext, topLevelStmt: PNode): PNode {.nimcall.}
 
@@ -48,8 +48,8 @@ proc makePass*(open: TPassOpen = nil,
 
 # the semantic checker needs these:
 var
-  gImportModule*: proc (m: PSym, fileIdx: int32): PSym {.nimcall.}
-  gIncludeFile*: proc (m: PSym, fileIdx: int32): PNode {.nimcall.}
+  gImportModule*: proc (graph: ModuleGraph; m: PSym, fileIdx: int32; cache: IdentCache): PSym {.nimcall.}
+  gIncludeFile*: proc (graph: ModuleGraph; m: PSym, fileIdx: int32; cache: IdentCache): PNode {.nimcall.}
 
 # implementation
 
@@ -90,28 +90,32 @@ proc registerPass*(p: TPass) =
   gPasses[gPassesLen] = p
   inc(gPassesLen)
 
-proc carryPass*(p: TPass, module: PSym, m: TPassData): TPassData =
-  var c = p.open(module)
+proc carryPass*(g: ModuleGraph; p: TPass, module: PSym; cache: IdentCache;
+                m: TPassData): TPassData =
+  var c = p.open(g, module, cache)
   result.input = p.process(c, m.input)
   result.closeOutput = if p.close != nil: p.close(c, m.closeOutput)
                        else: m.closeOutput
 
-proc carryPasses*(nodes: PNode, module: PSym, passes: TPasses) =
+proc carryPasses*(g: ModuleGraph; nodes: PNode, module: PSym;
+                  cache: IdentCache; passes: TPasses) =
   var passdata: TPassData
   passdata.input = nodes
   for pass in passes:
-    passdata = carryPass(pass, module, passdata)
+    passdata = carryPass(g, pass, module, cache, passdata)
 
-proc openPasses(a: var TPassContextArray, module: PSym) =
+proc openPasses(g: ModuleGraph; a: var TPassContextArray;
+                module: PSym; cache: IdentCache) =
   for i in countup(0, gPassesLen - 1):
     if not isNil(gPasses[i].open):
-      a[i] = gPasses[i].open(module)
+      a[i] = gPasses[i].open(g, module, cache)
     else: a[i] = nil
 
-proc openPassesCached(a: var TPassContextArray, module: PSym, rd: PRodReader) =
+proc openPassesCached(g: ModuleGraph; a: var TPassContextArray, module: PSym,
+                      rd: PRodReader) =
   for i in countup(0, gPassesLen - 1):
     if not isNil(gPasses[i].openCached):
-      a[i] = gPasses[i].openCached(module, rd)
+      a[i] = gPasses[i].openCached(g, module, rd)
       if a[i] != nil:
         a[i].fromCache = true
     else:
@@ -145,24 +149,35 @@ proc closePassesCached(a: var TPassContextArray) =
       m = gPasses[i].close(a[i], m)
     a[i] = nil                # free the memory here
 
+proc resolveMod(module, relativeTo: string): int32 =
+  let fullPath = findModule(module, relativeTo)
+  if fullPath.len == 0:
+    result = InvalidFileIDX
+  else:
+    result = fullPath.fileInfoIdx
+
 proc processImplicits(implicits: seq[string], nodeKind: TNodeKind,
-                      a: var TPassContextArray) =
+                      a: var TPassContextArray; m: PSym) =
+  # XXX fixme this should actually be relative to the config file!
+  let relativeTo = m.info.toFullPath
   for module in items(implicits):
-    var importStmt = newNodeI(nodeKind, gCmdLineInfo)
-    var str = newStrNode(nkStrLit, module)
-    str.info = gCmdLineInfo
-    importStmt.addSon str
-    if not processTopLevelStmt(importStmt, a): break
-
-proc processModule*(module: PSym, stream: PLLStream,
-                    rd: PRodReader): bool {.discardable.} =
+    # implicit imports should not lead to a module importing itself
+    if m.position != resolveMod(module, relativeTo):
+      var importStmt = newNodeI(nodeKind, gCmdLineInfo)
+      var str = newStrNode(nkStrLit, module)
+      str.info = gCmdLineInfo
+      importStmt.addSon str
+      if not processTopLevelStmt(importStmt, a): break
+
+proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream,
+                    rd: PRodReader; cache: IdentCache): bool {.discardable.} =
   var
     p: TParsers
     a: TPassContextArray
     s: PLLStream
     fileIdx = module.fileIdx
   if rd == nil:
-    openPasses(a, module)
+    openPasses(graph, a, module, cache)
     if stream == nil:
       let filename = fileIdx.toFullPathConsiderDirty
       s = llStreamOpen(filename, fmRead)
@@ -172,15 +187,15 @@ proc processModule*(module: PSym, stream: PLLStream,
     else:
       s = stream
     while true:
-      openParsers(p, fileIdx, s)
+      openParsers(p, fileIdx, s, cache)
 
       if sfSystemModule notin module.flags:
         # XXX what about caching? no processing then? what if I change the
         # modules to include between compilation runs? we'd need to track that
         # in ROD files. I think we should enable this feature only
         # for the interactive mode.
-        processImplicits implicitImports, nkImportStmt, a
-        processImplicits implicitIncludes, nkIncludeStmt, a
+        processImplicits implicitImports, nkImportStmt, a, module
+        processImplicits implicitIncludes, nkIncludeStmt, a, module
 
       while true:
         var n = parseTopLevelStmt(p)
@@ -202,7 +217,7 @@ proc processModule*(module: PSym, stream: PLLStream,
     # id synchronization point for more consistent code generation:
     idSynchronizationPoint(1000)
   else:
-    openPassesCached(a, module, rd)
+    openPassesCached(graph, a, module, rd)
     var n = loadInitSection(rd)
     for i in countup(0, sonsLen(n) - 1): processTopLevelStmtCached(n.sons[i], a)
     closePassesCached(a)
diff --git a/compiler/pbraces.nim b/compiler/pbraces.nim
index 00f83a11e..df9204be6 100644
--- a/compiler/pbraces.nim
+++ b/compiler/pbraces.nim
@@ -1,18 +1,1780 @@
 #
 #
 #           The Nim Compiler
-#        (c) Copyright 2012 Andreas Rumpf
+#        (c) Copyright 2016 Andreas Rumpf
 #
 #    See the file "copying.txt", included in this
 #    distribution, for details about the copyright.
 #
 
+# This module implements the parser of the braces Nim syntax.
+
 import
-  llstream, lexer, parser, idents, strutils, ast, msgs
+  llstream, lexer, idents, strutils, ast, astalgo, msgs
+
+from parser import TParser
+
+proc getTok(p: var TParser) =
+  ## Get the next token from the parser's lexer, and store it in the parser's
+  ## `tok` member.
+  rawGetTok(p.lex, p.tok)
+
+proc openParser*(p: var TParser, fileIdx: int32, inputStream: PLLStream;
+                 cache: IdentCache) =
+  ## Open a parser, using the given arguments to set up its internal state.
+  ##
+  initToken(p.tok)
+  openLexer(p.lex, fileIdx, inputStream, cache)
+  getTok(p)                   # read the first token
+  p.lex.allowTabs = true
+
+proc openParser*(p: var TParser, filename: string, inputStream: PLLStream;
+                 cache: IdentCache) =
+  openParser(p, filename.fileInfoIdx, inputStream, cache)
+
+proc closeParser*(p: var TParser) =
+  ## Close a parser, freeing up its resources.
+  closeLexer(p.lex)
+
+proc parMessage(p: TParser, msg: TMsgKind, arg = "") =
+  ## Produce and emit the parser message `arg` to output.
+  lexMessageTok(p.lex, msg, p.tok, arg)
+
+proc parMessage(p: TParser, msg: TMsgKind, tok: TToken) =
+  ## Produce and emit a parser message to output about the token `tok`
+  parMessage(p, msg, prettyTok(tok))
+
+proc rawSkipComment(p: var TParser, node: PNode) =
+  if p.tok.tokType == tkComment:
+    if node != nil:
+      if node.comment == nil: node.comment = ""
+      add(node.comment, p.tok.literal)
+    else:
+      parMessage(p, errInternal, "skipComment")
+    getTok(p)
+
+proc skipComment(p: var TParser, node: PNode) =
+  rawSkipComment(p, node)
+
+proc flexComment(p: var TParser, node: PNode) =
+  rawSkipComment(p, node)
+
+proc skipInd(p: var TParser) = discard
+proc optPar(p: var TParser) = discard
+
+proc optInd(p: var TParser, n: PNode) =
+  skipComment(p, n)
+
+proc getTokNoInd(p: var TParser) =
+  getTok(p)
+
+proc expectIdentOrKeyw(p: TParser) =
+  if p.tok.tokType != tkSymbol and not isKeyword(p.tok.tokType):
+    lexMessage(p.lex, errIdentifierExpected, prettyTok(p.tok))
+
+proc expectIdent(p: TParser) =
+  if p.tok.tokType != tkSymbol:
+    lexMessage(p.lex, errIdentifierExpected, prettyTok(p.tok))
+
+proc eat(p: var TParser, tokType: TTokType) =
+  ## Move the parser to the next token if the current token is of type
+  ## `tokType`, otherwise error.
+  if p.tok.tokType == tokType:
+    getTok(p)
+  else:
+    lexMessageTok(p.lex, errTokenExpected, p.tok, TokTypeToStr[tokType])
+
+proc parLineInfo(p: TParser): TLineInfo =
+  ## Retrieve the line information associated with the parser's current state.
+  result = getLineInfo(p.lex, p.tok)
+
+proc indAndComment(p: var TParser, n: PNode) =
+  rawSkipComment(p, n)
+
+proc newNodeP(kind: TNodeKind, p: TParser): PNode =
+  result = newNodeI(kind, parLineInfo(p))
+
+proc newIntNodeP(kind: TNodeKind, intVal: BiggestInt, p: TParser): PNode =
+  result = newNodeP(kind, p)
+  result.intVal = intVal
+
+proc newFloatNodeP(kind: TNodeKind, floatVal: BiggestFloat,
+                   p: TParser): PNode =
+  result = newNodeP(kind, p)
+  result.floatVal = floatVal
+
+proc newStrNodeP(kind: TNodeKind, strVal: string, p: TParser): PNode =
+  result = newNodeP(kind, p)
+  result.strVal = strVal
+
+proc newIdentNodeP(ident: PIdent, p: TParser): PNode =
+  result = newNodeP(nkIdent, p)
+  result.ident = ident
+
+proc parseExpr(p: var TParser): PNode
+proc parseStmt(p: var TParser): PNode
+proc parseTypeDesc(p: var TParser): PNode
+proc parseDoBlocks(p: var TParser, call: PNode)
+proc parseParamList(p: var TParser, retColon = true): PNode
+proc parseStmtPragma(p: var TParser): PNode
+proc parseCase(p: var TParser): PNode
+proc parseTry(p: var TParser): PNode
+
+proc isSigilLike(tok: TToken): bool {.inline.} =
+  result = tok.tokType == tkOpr and tok.ident.s[0] == '@'
+
+proc isAt(tok: TToken): bool {.inline.} =
+  tok.tokType == tkOpr and tok.ident.s == "@" and tok.strongSpaceB == 0
+
+proc isRightAssociative(tok: TToken): bool {.inline.} =
+  ## Determines whether the token is right assocative.
+  result = tok.tokType == tkOpr and tok.ident.s[0] == '^'
+  # or (let L = tok.ident.s.len; L > 1 and tok.ident.s[L-1] == '>'))
+
+proc getPrecedence(tok: TToken): int =
+  ## Calculates the precedence of the given token.
+  template considerStrongSpaces(x): untyped = x
+
+  case tok.tokType
+  of tkOpr:
+    let L = tok.ident.s.len
+    let relevantChar = tok.ident.s[0]
+
+    # arrow like?
+    if L > 1 and tok.ident.s[L-1] == '>' and
+      tok.ident.s[L-2] in {'-', '~', '='}: return considerStrongSpaces(1)
+
+    template considerAsgn(value: untyped) =
+      result = if tok.ident.s[L-1] == '=': 1 else: value
+
+    case relevantChar
+    of '$', '^': considerAsgn(10)
+    of '*', '%', '/', '\\': considerAsgn(9)
+    of '~': result = 8
+    of '+', '-', '|': considerAsgn(8)
+    of '&': considerAsgn(7)
+    of '=', '<', '>', '!': result = 5
+    of '.': considerAsgn(6)
+    of '?': result = 2
+    else: considerAsgn(2)
+  of tkDiv, tkMod, tkShl, tkShr: result = 9
+  of tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs: result = 5
+  of tkDotDot: result = 6
+  of tkAnd: result = 4
+  of tkOr, tkXor, tkPtr, tkRef: result = 3
+  else: return -10
+  result = considerStrongSpaces(result)
+
+proc isOperator(tok: TToken): bool =
+  ## Determines if the given token is an operator type token.
+  tok.tokType in {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs,
+                  tkIsnot, tkNot, tkOf, tkAs, tkDotDot, tkAnd, tkOr, tkXor}
+
+proc isUnary(p: TParser): bool =
+  ## Check if the current parser token is a unary operator
+  if p.tok.tokType in {tkOpr, tkDotDot}:
+      result = true
+
+proc checkBinary(p: TParser) {.inline.} =
+  ## Check if the current parser token is a binary operator.
+  # we don't check '..' here as that's too annoying
+  discard
+
+#| module = stmt ^* (';' / IND{=})
+#|
+#| comma = ',' COMMENT?
+#| semicolon = ';' COMMENT?
+#| colon = ':' COMMENT?
+#| colcom = ':' COMMENT?
+#|
+#| operator =  OP0 | OP1 | OP2 | OP3 | OP4 | OP5 | OP6 | OP7 | OP8 | OP9
+#|          | 'or' | 'xor' | 'and'
+#|          | 'is' | 'isnot' | 'in' | 'notin' | 'of'
+#|          | 'div' | 'mod' | 'shl' | 'shr' | 'not' | 'static' | '..'
+#|
+#| prefixOperator = operator
+#|
+#| optInd = COMMENT?
+#| optPar = (IND{>} | IND{=})?
+#|
+#| simpleExpr = arrowExpr (OP0 optInd arrowExpr)*
+#| arrowExpr = assignExpr (OP1 optInd assignExpr)*
+#| assignExpr = orExpr (OP2 optInd orExpr)*
+#| orExpr = andExpr (OP3 optInd andExpr)*
+#| andExpr = cmpExpr (OP4 optInd cmpExpr)*
+#| cmpExpr = sliceExpr (OP5 optInd sliceExpr)*
+#| sliceExpr = ampExpr (OP6 optInd ampExpr)*
+#| ampExpr = plusExpr (OP7 optInd plusExpr)*
+#| plusExpr = mulExpr (OP8 optInd mulExpr)*
+#| mulExpr = dollarExpr (OP9 optInd dollarExpr)*
+#| dollarExpr = primary (OP10 optInd primary)*
+
+proc colcom(p: var TParser, n: PNode) =
+  skipComment(p, n)
+
+proc parseSymbol(p: var TParser, allowNil = false): PNode =
+  #| symbol = '`' (KEYW|IDENT|literal|(operator|'('|')'|'['|']'|'{'|'}'|'=')+)+ '`'
+  #|        | IDENT | 'addr' | 'type'
+  case p.tok.tokType
+  of tkSymbol, tkAddr, tkType:
+    result = newIdentNodeP(p.tok.ident, p)
+    getTok(p)
+  of tkAccent:
+    result = newNodeP(nkAccQuoted, p)
+    getTok(p)
+    while true:
+      case p.tok.tokType
+      of tkAccent:
+        if result.len == 0:
+          parMessage(p, errIdentifierExpected, p.tok)
+        break
+      of tkOpr, tkDot, tkDotDot, tkEquals, tkParLe..tkParDotRi:
+        var accm = ""
+        while p.tok.tokType in {tkOpr, tkDot, tkDotDot, tkEquals,
+                                tkParLe..tkParDotRi}:
+          accm.add(tokToStr(p.tok))
+          getTok(p)
+        result.add(newIdentNodeP(p.lex.cache.getIdent(accm), p))
+      of tokKeywordLow..tokKeywordHigh, tkSymbol, tkIntLit..tkCharLit:
+        result.add(newIdentNodeP(p.lex.cache.getIdent(tokToStr(p.tok)), p))
+        getTok(p)
+      else:
+        parMessage(p, errIdentifierExpected, p.tok)
+        break
+    eat(p, tkAccent)
+  else:
+    if allowNil and p.tok.tokType == tkNil:
+      result = newNodeP(nkNilLit, p)
+      getTok(p)
+    else:
+      parMessage(p, errIdentifierExpected, p.tok)
+      # BUGFIX: We must consume a token here to prevent endless loops!
+      # But: this really sucks for idetools and keywords, so we don't do it
+      # if it is a keyword:
+      if not isKeyword(p.tok.tokType): getTok(p)
+      result = ast.emptyNode
+
+proc colonOrEquals(p: var TParser, a: PNode): PNode =
+  if p.tok.tokType == tkColon:
+    result = newNodeP(nkExprColonExpr, p)
+    getTok(p)
+    #optInd(p, result)
+    addSon(result, a)
+    addSon(result, parseExpr(p))
+  elif p.tok.tokType == tkEquals:
+    result = newNodeP(nkExprEqExpr, p)
+    getTok(p)
+    #optInd(p, result)
+    addSon(result, a)
+    addSon(result, parseExpr(p))
+  else:
+    result = a
+
+proc exprColonEqExpr(p: var TParser): PNode =
+  #| exprColonEqExpr = expr (':'|'=' expr)?
+  var a = parseExpr(p)
+  result = colonOrEquals(p, a)
+
+proc exprList(p: var TParser, endTok: TTokType, result: PNode) =
+  #| exprList = expr ^+ comma
+  getTok(p)
+  optInd(p, result)
+  while (p.tok.tokType != endTok) and (p.tok.tokType != tkEof):
+    var a = parseExpr(p)
+    addSon(result, a)
+    if p.tok.tokType != tkComma: break
+    getTok(p)
+    optInd(p, a)
+
+proc dotExpr(p: var TParser, a: PNode): PNode =
+  #| dotExpr = expr '.' optInd symbol
+  var info = p.parLineInfo
+  getTok(p)
+  result = newNodeI(nkDotExpr, info)
+  optInd(p, result)
+  addSon(result, a)
+  addSon(result, parseSymbol(p))
+
+proc qualifiedIdent(p: var TParser): PNode =
+  #| qualifiedIdent = symbol ('.' optInd symbol)?
+  result = parseSymbol(p)
+  if p.tok.tokType == tkDot: result = dotExpr(p, result)
+
+proc exprColonEqExprListAux(p: var TParser, endTok: TTokType, result: PNode) =
+  assert(endTok in {tkCurlyLe, tkCurlyRi, tkCurlyDotRi, tkBracketRi, tkParRi})
+  getTok(p)
+  optInd(p, result)
+  while p.tok.tokType != endTok and p.tok.tokType != tkEof:
+    var a = exprColonEqExpr(p)
+    addSon(result, a)
+    if p.tok.tokType != tkComma: break
+    getTok(p)
+    skipComment(p, a)
+  optPar(p)
+  eat(p, endTok)
+
+proc exprColonEqExprList(p: var TParser, kind: TNodeKind,
+                         endTok: TTokType): PNode =
+  #| exprColonEqExprList = exprColonEqExpr (comma exprColonEqExpr)* (comma)?
+  result = newNodeP(kind, p)
+  exprColonEqExprListAux(p, endTok, result)
+
+proc setOrTableConstr(p: var TParser): PNode =
+  result = newNodeP(nkCurly, p)
+  getTok(p)
+  optInd(p, result)
+  if p.tok.tokType == tkColon:
+    getTok(p) # skip ':'
+    result.kind = nkTableConstr
+  else:
+    while p.tok.tokType notin {tkBracketDotRi, tkEof}:
+      var a = exprColonEqExpr(p)
+      if a.kind == nkExprColonExpr: result.kind = nkTableConstr
+      addSon(result, a)
+      if p.tok.tokType != tkComma: break
+      getTok(p)
+      skipComment(p, a)
+  optPar(p)
+  eat(p, tkBracketDotRi)
+
+proc parseCast(p: var TParser): PNode =
+  #| castExpr = 'cast' '[' optInd typeDesc optPar ']' '(' optInd expr optPar ')'
+  result = newNodeP(nkCast, p)
+  getTok(p)
+  eat(p, tkBracketLe)
+  optInd(p, result)
+  addSon(result, parseTypeDesc(p))
+  optPar(p)
+  eat(p, tkBracketRi)
+  eat(p, tkParLe)
+  optInd(p, result)
+  addSon(result, parseExpr(p))
+  optPar(p)
+  eat(p, tkParRi)
+
+proc setBaseFlags(n: PNode, base: TNumericalBase) =
+  case base
+  of base10: discard
+  of base2: incl(n.flags, nfBase2)
+  of base8: incl(n.flags, nfBase8)
+  of base16: incl(n.flags, nfBase16)
+
+proc parseGStrLit(p: var TParser, a: PNode): PNode =
+  case p.tok.tokType
+  of tkGStrLit:
+    result = newNodeP(nkCallStrLit, p)
+    addSon(result, a)
+    addSon(result, newStrNodeP(nkRStrLit, p.tok.literal, p))
+    getTok(p)
+  of tkGTripleStrLit:
+    result = newNodeP(nkCallStrLit, p)
+    addSon(result, a)
+    addSon(result, newStrNodeP(nkTripleStrLit, p.tok.literal, p))
+    getTok(p)
+  else:
+    result = a
+
+type
+  TPrimaryMode = enum pmNormal, pmTypeDesc, pmTypeDef, pmSkipSuffix
+
+proc complexOrSimpleStmt(p: var TParser): PNode
+proc simpleExpr(p: var TParser, mode = pmNormal): PNode
+
+proc semiStmtList(p: var TParser, result: PNode) =
+  inc p.inSemiStmtList
+  result.add(complexOrSimpleStmt(p))
+  while p.tok.tokType == tkSemiColon:
+    getTok(p)
+    optInd(p, result)
+    result.add(complexOrSimpleStmt(p))
+  dec p.inSemiStmtList
+  result.kind = nkStmtListExpr
+
+proc parsePar(p: var TParser): PNode =
+  #| parKeyw = 'discard' | 'include' | 'if' | 'while' | 'case' | 'try'
+  #|         | 'finally' | 'except' | 'for' | 'block' | 'const' | 'let'
+  #|         | 'when' | 'var' | 'mixin'
+  #| par = '(' optInd
+  #|           ( &parKeyw complexOrSimpleStmt ^+ ';'
+  #|           | ';' complexOrSimpleStmt ^+ ';'
+  #|           | pragmaStmt
+  #|           | simpleExpr ( ('=' expr (';' complexOrSimpleStmt ^+ ';' )? )
+  #|                        | (':' expr (',' exprColonEqExpr     ^+ ',' )? ) ) )
+  #|           optPar ')'
+  #
+  # unfortunately it's ambiguous: (expr: expr) vs (exprStmt); however a
+  # leading ';' could be used to enforce a 'stmt' context ...
+  result = newNodeP(nkPar, p)
+  getTok(p)
+  optInd(p, result)
+  if p.tok.tokType in {tkDiscard, tkInclude, tkIf, tkWhile, tkCase,
+                       tkTry, tkDefer, tkFinally, tkExcept, tkFor, tkBlock,
+                       tkConst, tkLet, tkWhen, tkVar,
+                       tkMixin}:
+    # XXX 'bind' used to be an expression, so we exclude it here;
+    # tests/reject/tbind2 fails otherwise.
+    semiStmtList(p, result)
+  elif p.tok.tokType == tkSemiColon:
+    # '(;' enforces 'stmt' context:
+    getTok(p)
+    optInd(p, result)
+    semiStmtList(p, result)
+  elif p.tok.tokType == tkCurlyDotLe:
+    result.add(parseStmtPragma(p))
+  elif p.tok.tokType != tkParRi:
+    var a = simpleExpr(p)
+    if p.tok.tokType == tkEquals:
+      # special case: allow assignments
+      getTok(p)
+      optInd(p, result)
+      let b = parseExpr(p)
+      let asgn = newNodeI(nkAsgn, a.info, 2)
+      asgn.sons[0] = a
+      asgn.sons[1] = b
+      result.add(asgn)
+      if p.tok.tokType == tkSemiColon:
+        semiStmtList(p, result)
+    elif p.tok.tokType == tkSemiColon:
+      # stmt context:
+      result.add(a)
+      semiStmtList(p, result)
+    else:
+      a = colonOrEquals(p, a)
+      result.add(a)
+      if p.tok.tokType == tkComma:
+        getTok(p)
+        skipComment(p, a)
+        while p.tok.tokType != tkParRi and p.tok.tokType != tkEof:
+          var a = exprColonEqExpr(p)
+          addSon(result, a)
+          if p.tok.tokType != tkComma: break
+          getTok(p)
+          skipComment(p, a)
+  optPar(p)
+  eat(p, tkParRi)
+
+proc identOrLiteral(p: var TParser, mode: TPrimaryMode): PNode =
+  #| literal = | INT_LIT | INT8_LIT | INT16_LIT | INT32_LIT | INT64_LIT
+  #|           | UINT_LIT | UINT8_LIT | UINT16_LIT | UINT32_LIT | UINT64_LIT
+  #|           | FLOAT_LIT | FLOAT32_LIT | FLOAT64_LIT
+  #|           | STR_LIT | RSTR_LIT | TRIPLESTR_LIT
+  #|           | CHAR_LIT
+  #|           | NIL
+  #| generalizedLit = GENERALIZED_STR_LIT | GENERALIZED_TRIPLESTR_LIT
+  #| identOrLiteral = generalizedLit | symbol | literal
+  #|                | par | arrayConstr | setOrTableConstr
+  #|                | castExpr
+  #| tupleConstr = '(' optInd (exprColonEqExpr comma?)* optPar ')'
+  #| arrayConstr = '[' optInd (exprColonEqExpr comma?)* optPar ']'
+  case p.tok.tokType
+  of tkSymbol, tkType, tkAddr:
+    result = newIdentNodeP(p.tok.ident, p)
+    getTok(p)
+    result = parseGStrLit(p, result)
+  of tkAccent:
+    result = parseSymbol(p)       # literals
+  of tkIntLit:
+    result = newIntNodeP(nkIntLit, p.tok.iNumber, p)
+    setBaseFlags(result, p.tok.base)
+    getTok(p)
+  of tkInt8Lit:
+    result = newIntNodeP(nkInt8Lit, p.tok.iNumber, p)
+    setBaseFlags(result, p.tok.base)
+    getTok(p)
+  of tkInt16Lit:
+    result = newIntNodeP(nkInt16Lit, p.tok.iNumber, p)
+    setBaseFlags(result, p.tok.base)
+    getTok(p)
+  of tkInt32Lit:
+    result = newIntNodeP(nkInt32Lit, p.tok.iNumber, p)
+    setBaseFlags(result, p.tok.base)
+    getTok(p)
+  of tkInt64Lit:
+    result = newIntNodeP(nkInt64Lit, p.tok.iNumber, p)
+    setBaseFlags(result, p.tok.base)
+    getTok(p)
+  of tkUIntLit:
+    result = newIntNodeP(nkUIntLit, p.tok.iNumber, p)
+    setBaseFlags(result, p.tok.base)
+    getTok(p)
+  of tkUInt8Lit:
+    result = newIntNodeP(nkUInt8Lit, p.tok.iNumber, p)
+    setBaseFlags(result, p.tok.base)
+    getTok(p)
+  of tkUInt16Lit:
+    result = newIntNodeP(nkUInt16Lit, p.tok.iNumber, p)
+    setBaseFlags(result, p.tok.base)
+    getTok(p)
+  of tkUInt32Lit:
+    result = newIntNodeP(nkUInt32Lit, p.tok.iNumber, p)
+    setBaseFlags(result, p.tok.base)
+    getTok(p)
+  of tkUInt64Lit:
+    result = newIntNodeP(nkUInt64Lit, p.tok.iNumber, p)
+    setBaseFlags(result, p.tok.base)
+    getTok(p)
+  of tkFloatLit:
+    result = newFloatNodeP(nkFloatLit, p.tok.fNumber, p)
+    setBaseFlags(result, p.tok.base)
+    getTok(p)
+  of tkFloat32Lit:
+    result = newFloatNodeP(nkFloat32Lit, p.tok.fNumber, p)
+    setBaseFlags(result, p.tok.base)
+    getTok(p)
+  of tkFloat64Lit:
+    result = newFloatNodeP(nkFloat64Lit, p.tok.fNumber, p)
+    setBaseFlags(result, p.tok.base)
+    getTok(p)
+  of tkFloat128Lit:
+    result = newFloatNodeP(nkFloat128Lit, p.tok.fNumber, p)
+    setBaseFlags(result, p.tok.base)
+    getTok(p)
+  of tkStrLit:
+    result = newStrNodeP(nkStrLit, p.tok.literal, p)
+    getTok(p)
+  of tkRStrLit:
+    result = newStrNodeP(nkRStrLit, p.tok.literal, p)
+    getTok(p)
+  of tkTripleStrLit:
+    result = newStrNodeP(nkTripleStrLit, p.tok.literal, p)
+    getTok(p)
+  of tkCharLit:
+    result = newIntNodeP(nkCharLit, ord(p.tok.literal[0]), p)
+    getTok(p)
+  of tkNil:
+    result = newNodeP(nkNilLit, p)
+    getTok(p)
+  of tkParLe:
+    # () constructor
+    if mode in {pmTypeDesc, pmTypeDef}:
+      result = exprColonEqExprList(p, nkPar, tkParRi)
+    else:
+      result = parsePar(p)
+  of tkBracketDotLe:
+    # {} constructor
+    result = setOrTableConstr(p)
+  of tkBracketLe:
+    # [] constructor
+    result = exprColonEqExprList(p, nkBracket, tkBracketRi)
+  of tkCast:
+    result = parseCast(p)
+  else:
+    parMessage(p, errExprExpected, p.tok)
+    getTok(p)  # we must consume a token here to prevend endless loops!
+    result = ast.emptyNode
+
+proc namedParams(p: var TParser, callee: PNode,
+                 kind: TNodeKind, endTok: TTokType): PNode =
+  let a = callee
+  result = newNodeP(kind, p)
+  addSon(result, a)
+  exprColonEqExprListAux(p, endTok, result)
+
+proc parseMacroColon(p: var TParser, x: PNode): PNode
+proc primarySuffix(p: var TParser, r: PNode): PNode =
+  #| primarySuffix = '(' (exprColonEqExpr comma?)* ')' doBlocks?
+  #|       | doBlocks
+  #|       | '.' optInd symbol generalizedLit?
+  #|       | '[' optInd indexExprList optPar ']'
+  #|       | '{' optInd indexExprList optPar '}'
+  #|       | &( '`'|IDENT|literal|'cast'|'addr'|'type') expr # command syntax
+  result = r
+
+  template somePar() = discard
+  while p.tok.indent < 0:
+    case p.tok.tokType
+    of tkParLe:
+      somePar()
+      result = namedParams(p, result, nkCall, tkParRi)
+      if result.len > 1 and result.sons[1].kind == nkExprColonExpr:
+        result.kind = nkObjConstr
+      else:
+        parseDoBlocks(p, result)
+    of tkDo:
+      var a = result
+      result = newNodeP(nkCall, p)
+      addSon(result, a)
+      parseDoBlocks(p, result)
+    of tkDot:
+      result = dotExpr(p, result)
+      result = parseGStrLit(p, result)
+    of tkBracketLe:
+      somePar()
+      result = namedParams(p, result, nkBracketExpr, tkBracketRi)
+    of tkBracketDotLe:
+      somePar()
+      result = namedParams(p, result, nkCurlyExpr, tkBracketDotRi)
+    of tkSymbol, tkAccent, tkIntLit..tkCharLit, tkNil, tkCast, tkAddr, tkType:
+      if p.inPragma == 0:
+        # actually parsing {.push hints:off.} as {.push(hints:off).} is a sweet
+        # solution, but pragmas.nim can't handle that
+        let a = result
+        result = newNodeP(nkCommand, p)
+        addSon(result, a)
+        when true:
+          addSon result, parseExpr(p)
+        else:
+          while p.tok.tokType != tkEof:
+            let x = parseExpr(p)
+            addSon(result, x)
+            if p.tok.tokType != tkComma: break
+            getTok(p)
+            optInd(p, x)
+          if p.tok.tokType == tkDo:
+            parseDoBlocks(p, result)
+          else:
+            result = parseMacroColon(p, result)
+      break
+    else:
+      break
+
+proc primary(p: var TParser, mode: TPrimaryMode): PNode
+proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode
+
+proc parseOperators(p: var TParser, headNode: PNode,
+                    limit: int, mode: TPrimaryMode): PNode =
+  result = headNode
+  # expand while operators have priorities higher than 'limit'
+  var opPrec = getPrecedence(p.tok)
+  let modeB = if mode == pmTypeDef: pmTypeDesc else: mode
+  # the operator itself must not start on a new line:
+  while opPrec >= limit and p.tok.indent < 0 and not isAt(p.tok):
+    checkBinary(p)
+    var leftAssoc = 1-ord(isRightAssociative(p.tok))
+    var a = newNodeP(nkInfix, p)
+    var opNode = newIdentNodeP(p.tok.ident, p) # skip operator:
+    getTok(p)
+    optInd(p, a)
+    # read sub-expression with higher priority:
+    var b = simpleExprAux(p, opPrec + leftAssoc, modeB)
+    addSon(a, opNode)
+    addSon(a, result)
+    addSon(a, b)
+    result = a
+    opPrec = getPrecedence(p.tok)
+
+proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode =
+  result = primary(p, mode)
+  result = parseOperators(p, result, limit, mode)
+
+proc simpleExpr(p: var TParser, mode = pmNormal): PNode =
+  result = simpleExprAux(p, -1, mode)
+
+proc parseIfExpr(p: var TParser, kind: TNodeKind): PNode =
+  #| condExpr = expr colcom expr optInd
+  #|         ('elif' expr colcom expr optInd)*
+  #|          'else' colcom expr
+  #| ifExpr = 'if' condExpr
+  #| whenExpr = 'when' condExpr
+  result = newNodeP(kind, p)
+  while true:
+    getTok(p)                 # skip `if`, `elif`
+    var branch = newNodeP(nkElifExpr, p)
+    addSon(branch, parseExpr(p))
+    colcom(p, branch)
+    addSon(branch, parseExpr(p))
+    optInd(p, branch)
+    addSon(result, branch)
+    if p.tok.tokType != tkElif: break
+  var branch = newNodeP(nkElseExpr, p)
+  eat(p, tkElse)
+  colcom(p, branch)
+  addSon(branch, parseExpr(p))
+  addSon(result, branch)
+
+proc parsePragma(p: var TParser): PNode =
+  result = newNodeP(nkPragma, p)
+  inc p.inPragma
+  if isAt(p.tok):
+    while isAt(p.tok):
+      getTok(p)
+      var a = parseExpr(p)
+      optInd(p, a)
+      if a.kind in nkCallKinds and a.len == 2:
+        let repaired = newNodeI(nkExprColonExpr, a.info)
+        repaired.add a[0]
+        repaired.add a[1]
+        a = repaired
+      addSon(result, a)
+      skipComment(p, a)
+  else:
+    getTok(p)
+    optInd(p, result)
+    while p.tok.tokType notin {tkCurlyDotRi, tkCurlyRi, tkEof}:
+      var a = exprColonEqExpr(p)
+      addSon(result, a)
+      if p.tok.tokType == tkComma:
+        getTok(p)
+        skipComment(p, a)
+    optPar(p)
+    if p.tok.tokType in {tkCurlyDotRi, tkCurlyRi}: getTok(p)
+    else: parMessage(p, errTokenExpected, ".}")
+  dec p.inPragma
+
+proc identVis(p: var TParser; allowDot=false): PNode =
+  #| identVis = symbol opr?  # postfix position
+  #| identVisDot = symbol '.' optInd symbol opr?
+  var a = parseSymbol(p)
+  if p.tok.tokType == tkOpr:
+    result = newNodeP(nkPostfix, p)
+    addSon(result, newIdentNodeP(p.tok.ident, p))
+    addSon(result, a)
+    getTok(p)
+  elif p.tok.tokType == tkDot and allowDot:
+    result = dotExpr(p, a)
+  else:
+    result = a
+
+proc identWithPragma(p: var TParser; allowDot=false): PNode =
+  #| identWithPragma = identVis pragma?
+  #| identWithPragmaDot = identVisDot pragma?
+  var a = identVis(p, allowDot)
+  if p.tok.tokType == tkCurlyDotLe or isAt(p.tok):
+    result = newNodeP(nkPragmaExpr, p)
+    addSon(result, a)
+    addSon(result, parsePragma(p))
+  else:
+    result = a
+
+type
+  TDeclaredIdentFlag = enum
+    withPragma,               # identifier may have pragma
+    withBothOptional          # both ':' and '=' parts are optional
+  TDeclaredIdentFlags = set[TDeclaredIdentFlag]
+
+proc parseIdentColonEquals(p: var TParser, flags: TDeclaredIdentFlags): PNode =
+  #| declColonEquals = identWithPragma (comma identWithPragma)* comma?
+  #|                   (':' optInd typeDesc)? ('=' optInd expr)?
+  #| identColonEquals = ident (comma ident)* comma?
+  #|      (':' optInd typeDesc)? ('=' optInd expr)?)
+  var a: PNode
+  result = newNodeP(nkIdentDefs, p)
+  while true:
+    case p.tok.tokType
+    of tkSymbol, tkAccent:
+      if withPragma in flags: a = identWithPragma(p)
+      else: a = parseSymbol(p)
+      if a.kind == nkEmpty: return
+    else: break
+    addSon(result, a)
+    if p.tok.tokType != tkComma: break
+    getTok(p)
+    optInd(p, a)
+  if p.tok.tokType == tkColon:
+    getTok(p)
+    optInd(p, result)
+    addSon(result, parseTypeDesc(p))
+  else:
+    addSon(result, ast.emptyNode)
+    if p.tok.tokType != tkEquals and withBothOptional notin flags:
+      parMessage(p, errColonOrEqualsExpected, p.tok)
+  if p.tok.tokType == tkEquals:
+    getTok(p)
+    optInd(p, result)
+    addSon(result, parseExpr(p))
+  else:
+    addSon(result, ast.emptyNode)
+
+proc parseTuple(p: var TParser): PNode =
+  result = newNodeP(nkTupleTy, p)
+  getTok(p)
+  if p.tok.tokType in {tkBracketLe, tkCurlyLe}:
+    let usedCurly = p.tok.tokType == tkCurlyLe
+    getTok(p)
+    optInd(p, result)
+    while p.tok.tokType in {tkSymbol, tkAccent}:
+      var a = parseIdentColonEquals(p, {})
+      addSon(result, a)
+      if p.tok.tokType notin {tkComma, tkSemiColon}: break
+      getTok(p)
+      skipComment(p, a)
+    optPar(p)
+    if usedCurly: eat(p, tkCurlyRi)
+    else: eat(p, tkBracketRi)
+  else:
+    result = newNodeP(nkTupleClassTy, p)
+
+proc parseParamList(p: var TParser, retColon = true): PNode =
+  #| paramList = '(' declColonEquals ^* (comma/semicolon) ')'
+  #| paramListArrow = paramList? ('->' optInd typeDesc)?
+  #| paramListColon = paramList? (':' optInd typeDesc)?
+  var a: PNode
+  result = newNodeP(nkFormalParams, p)
+  addSon(result, ast.emptyNode) # return type
+  let hasParLe = p.tok.tokType == tkParLe and p.tok.indent < 0
+  if hasParLe:
+    getTok(p)
+    optInd(p, result)
+    while true:
+      case p.tok.tokType
+      of tkSymbol, tkAccent:
+        a = parseIdentColonEquals(p, {withBothOptional, withPragma})
+      of tkParRi:
+        break
+      else:
+        parMessage(p, errTokenExpected, ")")
+        break
+      addSon(result, a)
+      if p.tok.tokType notin {tkComma, tkSemiColon}: break
+      getTok(p)
+      skipComment(p, a)
+    optPar(p)
+    eat(p, tkParRi)
+  let hasRet = if retColon: p.tok.tokType == tkColon
+               else: p.tok.tokType == tkOpr and p.tok.ident.s == "->"
+  if hasRet and p.tok.indent < 0:
+    getTok(p)
+    optInd(p, result)
+    result.sons[0] = parseTypeDesc(p)
+  elif not retColon and not hasParle:
+    # Mark as "not there" in order to mark for deprecation in the semantic pass:
+    result = ast.emptyNode
+
+proc optPragmas(p: var TParser): PNode =
+  if p.tok.tokType == tkCurlyDotLe or isAt(p.tok):
+    result = parsePragma(p)
+  else:
+    result = ast.emptyNode
+
+proc parseDoBlock(p: var TParser): PNode =
+  #| doBlock = 'do' paramListArrow pragmas? colcom stmt
+  let info = parLineInfo(p)
+  getTok(p)
+  let params = parseParamList(p, retColon=false)
+  let pragmas = optPragmas(p)
+  colcom(p, result)
+  result = newProcNode(nkDo, info, parseStmt(p),
+                       params = params,
+                       pragmas = pragmas)
+
+proc parseDoBlocks(p: var TParser, call: PNode) =
+  #| doBlocks = doBlock ^* IND{=}
+  while p.tok.tokType == tkDo:
+    addSon(call, parseDoBlock(p))
+
+proc parseCurlyStmt(p: var TParser): PNode =
+  result = newNodeP(nkStmtList, p)
+  eat(p, tkCurlyLe)
+  result.add parseStmt(p)
+  while p.tok.tokType notin {tkEof, tkCurlyRi}:
+    if p.tok.tokType == tkSemicolon: getTok(p)
+    elif p.tok.indent < 0: break
+    result.add parseStmt(p)
+  eat(p, tkCurlyRi)
+
+proc parseProcExpr(p: var TParser, isExpr: bool): PNode =
+  #| procExpr = 'proc' paramListColon pragmas? ('=' COMMENT? stmt)?
+  # either a proc type or a anonymous proc
+  let info = parLineInfo(p)
+  getTok(p)
+  let hasSignature = p.tok.tokType in {tkParLe, tkColon} and p.tok.indent < 0
+  let params = parseParamList(p)
+  let pragmas = optPragmas(p)
+  if p.tok.tokType == tkCurlyLe and isExpr:
+    result = newProcNode(nkLambda, info, parseCurlyStmt(p),
+                         params = params,
+                         pragmas = pragmas)
+  else:
+    result = newNodeI(nkProcTy, info)
+    if hasSignature:
+      addSon(result, params)
+      addSon(result, pragmas)
+
+proc isExprStart(p: TParser): bool =
+  case p.tok.tokType
+  of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf,
+     tkProc, tkIterator, tkBind, tkAddr,
+     tkParLe, tkBracketLe, tkCurlyLe, tkIntLit..tkCharLit, tkVar, tkRef, tkPtr,
+     tkTuple, tkObject, tkType, tkWhen, tkCase, tkOut:
+    result = true
+  else: result = false
+
+proc parseSymbolList(p: var TParser, result: PNode, allowNil = false) =
+  while true:
+    var s = parseSymbol(p, allowNil)
+    if s.kind == nkEmpty: break
+    addSon(result, s)
+    if p.tok.tokType != tkComma: break
+    getTok(p)
+    optInd(p, s)
+
+proc parseTypeDescKAux(p: var TParser, kind: TNodeKind,
+                       mode: TPrimaryMode): PNode =
+  #| distinct = 'distinct' optInd typeDesc
+  result = newNodeP(kind, p)
+  getTok(p)
+  optInd(p, result)
+  if not isOperator(p.tok) and isExprStart(p):
+    addSon(result, primary(p, mode))
+  if kind == nkDistinctTy and p.tok.tokType in {tkWith, tkWithout}:
+    let nodeKind = if p.tok.tokType == tkWith: nkWith
+                   else: nkWithout
+    getTok(p)
+    let list = newNodeP(nodeKind, p)
+    result.addSon list
+    parseSymbolList(p, list, allowNil = true)
+
+proc parseExpr(p: var TParser): PNode =
+  #| expr = (ifExpr
+  #|       | whenExpr
+  #|       | caseExpr
+  #|       | tryExpr)
+  #|       / simpleExpr
+  case p.tok.tokType:
+  of tkIf: result = parseIfExpr(p, nkIfExpr)
+  of tkWhen: result = parseIfExpr(p, nkWhenExpr)
+  of tkCase: result = parseCase(p)
+  of tkTry: result = parseTry(p)
+  else: result = simpleExpr(p)
+
+proc parseEnum(p: var TParser): PNode
+proc parseObject(p: var TParser): PNode
+proc parseTypeClass(p: var TParser): PNode
+
+proc primary(p: var TParser, mode: TPrimaryMode): PNode =
+  #| typeKeyw = 'var' | 'out' | 'ref' | 'ptr' | 'shared' | 'tuple'
+  #|          | 'proc' | 'iterator' | 'distinct' | 'object' | 'enum'
+  #| primary = typeKeyw typeDescK
+  #|         /  prefixOperator* identOrLiteral primarySuffix*
+  #|         / 'static' primary
+  #|         / 'bind' primary
+  if isOperator(p.tok):
+    let isSigil = isSigilLike(p.tok)
+    result = newNodeP(nkPrefix, p)
+    var a = newIdentNodeP(p.tok.ident, p)
+    addSon(result, a)
+    getTok(p)
+    optInd(p, a)
+    if isSigil:
+      #XXX prefix operators
+      addSon(result, primary(p, pmSkipSuffix))
+      result = primarySuffix(p, result)
+    else:
+      addSon(result, primary(p, pmNormal))
+    return
+
+  case p.tok.tokType:
+  of tkTuple: result = parseTuple(p)
+  of tkProc: result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef})
+  of tkIterator:
+    result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef})
+    if result.kind == nkLambda: result.kind = nkIteratorDef
+    else: result.kind = nkIteratorTy
+  of tkEnum:
+    if mode == pmTypeDef:
+      result = parseEnum(p)
+    else:
+      result = newNodeP(nkEnumTy, p)
+      getTok(p)
+  of tkObject:
+    if mode == pmTypeDef:
+      result = parseObject(p)
+    else:
+      result = newNodeP(nkObjectTy, p)
+      getTok(p)
+  of tkConcept:
+    if mode == pmTypeDef:
+      result = parseTypeClass(p)
+    else:
+      parMessage(p, errInvalidToken, p.tok)
+  of tkStatic:
+    let info = parLineInfo(p)
+    getTokNoInd(p)
+    let next = primary(p, pmNormal)
+    if next.kind == nkBracket and next.sonsLen == 1:
+      result = newNode(nkStaticTy, info, @[next.sons[0]])
+    else:
+      result = newNode(nkStaticExpr, info, @[next])
+  of tkBind:
+    result = newNodeP(nkBind, p)
+    getTok(p)
+    optInd(p, result)
+    addSon(result, primary(p, pmNormal))
+  of tkVar: result = parseTypeDescKAux(p, nkVarTy, mode)
+  of tkOut: result = parseTypeDescKAux(p, nkVarTy, mode)
+  of tkRef: result = parseTypeDescKAux(p, nkRefTy, mode)
+  of tkPtr: result = parseTypeDescKAux(p, nkPtrTy, mode)
+  of tkDistinct: result = parseTypeDescKAux(p, nkDistinctTy, mode)
+  else:
+    result = identOrLiteral(p, mode)
+    if mode != pmSkipSuffix:
+      result = primarySuffix(p, result)
+
+proc parseTypeDesc(p: var TParser): PNode =
+  #| typeDesc = simpleExpr
+  result = simpleExpr(p, pmTypeDesc)
+
+proc parseTypeDefAux(p: var TParser): PNode =
+  #| typeDefAux = simpleExpr
+  #|            | 'concept' typeClass
+  result = simpleExpr(p, pmTypeDef)
+
+proc makeCall(n: PNode): PNode =
+  ## Creates a call if the given node isn't already a call.
+  if n.kind in nkCallKinds:
+    result = n
+  else:
+    result = newNodeI(nkCall, n.info)
+    result.add n
+
+proc parseMacroColon(p: var TParser, x: PNode): PNode =
+  #| macroColon = ':' stmt? ( IND{=} 'of' exprList ':' stmt
+  #|                        | IND{=} 'elif' expr ':' stmt
+  #|                        | IND{=} 'except' exprList ':' stmt
+  #|                        | IND{=} 'else' ':' stmt )*
+  result = x
+  if p.tok.tokType == tkColon and p.tok.indent < 0:
+    result = makeCall(result)
+    getTok(p)
+    skipComment(p, result)
+    let stmtList = newNodeP(nkStmtList, p)
+    if p.tok.tokType notin {tkOf, tkElif, tkElse, tkExcept}:
+      let body = parseStmt(p)
+      stmtList.add body
+      #addSon(result, makeStmtList(body))
+    while true:
+      var b: PNode
+      case p.tok.tokType
+      of tkOf:
+        b = newNodeP(nkOfBranch, p)
+        exprList(p, tkCurlyLe, b)
+      of tkElif:
+        b = newNodeP(nkElifBranch, p)
+        getTok(p)
+        optInd(p, b)
+        addSon(b, parseExpr(p))
+      of tkExcept:
+        b = newNodeP(nkExceptBranch, p)
+        exprList(p, tkCurlyLe, b)
+      of tkElse:
+        b = newNodeP(nkElse, p)
+        getTok(p)
+      else: break
+      addSon(b, parseCurlyStmt(p))
+      addSon(stmtList, b)
+      if b.kind == nkElse: break
+    if stmtList.len == 1 and stmtList[0].kind == nkStmtList:
+      # to keep backwards compatibility (see tests/vm/tstringnil)
+      result.add stmtList[0]
+    else:
+      result.add stmtList
+
+proc parseExprStmt(p: var TParser): PNode =
+  #| exprStmt = simpleExpr
+  #|          (( '=' optInd expr )
+  #|          / ( expr ^+ comma
+  #|              doBlocks
+  #|               / macroColon
+  #|            ))?
+  var a = simpleExpr(p)
+  if p.tok.tokType == tkEquals:
+    getTok(p)
+    optInd(p, result)
+    var b = parseExpr(p)
+    result = newNodeI(nkAsgn, a.info)
+    addSon(result, a)
+    addSon(result, b)
+  else:
+    # simpleExpr parsed 'p a' from 'p a, b'?
+    if p.tok.indent < 0 and p.tok.tokType == tkComma and a.kind == nkCommand:
+      result = a
+      while true:
+        getTok(p)
+        optInd(p, result)
+        var e = parseExpr(p)
+        addSon(result, e)
+        if p.tok.tokType != tkComma: break
+    elif p.tok.indent < 0 and isExprStart(p):
+      if a.kind == nkCommand:
+        result = a
+      else:
+        result = newNode(nkCommand, a.info, @[a])
+      while true:
+        var e = parseExpr(p)
+        addSon(result, e)
+        if p.tok.tokType != tkComma: break
+        getTok(p)
+        optInd(p, result)
+    else:
+      result = a
+    if p.tok.tokType == tkDo and p.tok.indent < 0:
+      result = makeCall(result)
+      parseDoBlocks(p, result)
+      return result
+    result = parseMacroColon(p, result)
+
+proc parseModuleName(p: var TParser, kind: TNodeKind): PNode =
+  result = parseExpr(p)
+
+proc parseImport(p: var TParser, kind: TNodeKind): PNode =
+  #| importStmt = 'import' optInd expr
+  #|               ((comma expr)*
+  #|               / 'except' optInd (expr ^+ comma))
+  result = newNodeP(kind, p)
+  getTok(p)                   # skip `import` or `export`
+  optInd(p, result)
+  var a = parseModuleName(p, kind)
+  addSon(result, a)
+  if p.tok.tokType in {tkComma, tkExcept}:
+    if p.tok.tokType == tkExcept:
+      result.kind = succ(kind)
+    getTok(p)
+    optInd(p, result)
+    while true:
+      # was: while p.tok.tokType notin {tkEof, tkSad, tkDed}:
+      a = parseModuleName(p, kind)
+      if a.kind == nkEmpty: break
+      addSon(result, a)
+      if p.tok.tokType != tkComma: break
+      getTok(p)
+      optInd(p, a)
+  #expectNl(p)
+
+proc parseIncludeStmt(p: var TParser): PNode =
+  #| includeStmt = 'include' optInd expr ^+ comma
+  result = newNodeP(nkIncludeStmt, p)
+  getTok(p)                   # skip `import` or `include`
+  optInd(p, result)
+  while true:
+    # was: while p.tok.tokType notin {tkEof, tkSad, tkDed}:
+    var a = parseExpr(p)
+    if a.kind == nkEmpty: break
+    addSon(result, a)
+    if p.tok.tokType != tkComma: break
+    getTok(p)
+    optInd(p, a)
+  #expectNl(p)
+
+proc parseFromStmt(p: var TParser): PNode =
+  #| fromStmt = 'from' moduleName 'import' optInd expr (comma expr)*
+  result = newNodeP(nkFromStmt, p)
+  getTok(p)                   # skip `from`
+  optInd(p, result)
+  var a = parseModuleName(p, nkImportStmt)
+  addSon(result, a)           #optInd(p, a);
+  eat(p, tkImport)
+  optInd(p, result)
+  while true:
+    # p.tok.tokType notin {tkEof, tkSad, tkDed}:
+    a = parseExpr(p)
+    if a.kind == nkEmpty: break
+    addSon(result, a)
+    if p.tok.tokType != tkComma: break
+    getTok(p)
+    optInd(p, a)
+  #expectNl(p)
+
+proc parseReturnOrRaise(p: var TParser, kind: TNodeKind): PNode =
+  #| returnStmt = 'return' optInd expr?
+  #| raiseStmt = 'raise' optInd expr?
+  #| yieldStmt = 'yield' optInd expr?
+  #| discardStmt = 'discard' optInd expr?
+  #| breakStmt = 'break' optInd expr?
+  #| continueStmt = 'break' optInd expr?
+  result = newNodeP(kind, p)
+  getTok(p)
+  if p.tok.tokType == tkComment:
+    skipComment(p, result)
+    addSon(result, ast.emptyNode)
+  elif p.tok.indent >= 0 or not isExprStart(p):
+    # NL terminates:
+    addSon(result, ast.emptyNode)
+  else:
+    addSon(result, parseExpr(p))
+
+proc parseIfOrWhen(p: var TParser, kind: TNodeKind): PNode =
+  #| condStmt = expr colcom stmt COMMENT?
+  #|            (IND{=} 'elif' expr colcom stmt)*
+  #|            (IND{=} 'else' colcom stmt)?
+  #| ifStmt = 'if' condStmt
+  #| whenStmt = 'when' condStmt
+  result = newNodeP(kind, p)
+  while true:
+    getTok(p)                 # skip `if`, `when`, `elif`
+    var branch = newNodeP(nkElifBranch, p)
+    optInd(p, branch)
+    addSon(branch, parseExpr(p))
+    colcom(p, branch)
+    addSon(branch, parseCurlyStmt(p))
+    skipComment(p, branch)
+    addSon(result, branch)
+    if p.tok.tokType != tkElif: break
+  if p.tok.tokType == tkElse:
+    var branch = newNodeP(nkElse, p)
+    eat(p, tkElse)
+    addSon(branch, parseCurlyStmt(p))
+    addSon(result, branch)
+
+proc parseWhile(p: var TParser): PNode =
+  #| whileStmt = 'while' expr colcom stmt
+  result = newNodeP(nkWhileStmt, p)
+  getTok(p)
+  optInd(p, result)
+  addSon(result, parseExpr(p))
+  colcom(p, result)
+  addSon(result, parseCurlyStmt(p))
+
+proc parseCase(p: var TParser): PNode =
+  #| ofBranch = 'of' exprList colcom stmt
+  #| ofBranches = ofBranch (IND{=} ofBranch)*
+  #|                       (IND{=} 'elif' expr colcom stmt)*
+  #|                       (IND{=} 'else' colcom stmt)?
+  #| caseStmt = 'case' expr ':'? COMMENT?
+  #|             (IND{>} ofBranches DED
+  #|             | IND{=} ofBranches)
+  var
+    b: PNode
+    inElif= false
+  result = newNodeP(nkCaseStmt, p)
+  getTok(p)
+  addSon(result, parseExpr(p))
+  eat(p, tkCurlyLe)
+  skipComment(p, result)
+
+  while true:
+    case p.tok.tokType
+    of tkOf:
+      if inElif: break
+      b = newNodeP(nkOfBranch, p)
+      exprList(p, tkCurlyLe, b)
+    of tkElif:
+      inElif = true
+      b = newNodeP(nkElifBranch, p)
+      getTok(p)
+      optInd(p, b)
+      addSon(b, parseExpr(p))
+    of tkElse:
+      b = newNodeP(nkElse, p)
+      getTok(p)
+    else: break
+    skipComment(p, b)
+    addSon(b, parseCurlyStmt(p))
+    addSon(result, b)
+    if b.kind == nkElse: break
+  eat(p, tkCurlyRi)
+
+proc parseTry(p: var TParser): PNode =
+  #| tryStmt = 'try' colcom stmt &(IND{=}? 'except'|'finally')
+  #|            (IND{=}? 'except' exprList colcom stmt)*
+  #|            (IND{=}? 'finally' colcom stmt)?
+  #| tryExpr = 'try' colcom stmt &(optInd 'except'|'finally')
+  #|            (optInd 'except' exprList colcom stmt)*
+  #|            (optInd 'finally' colcom stmt)?
+  result = newNodeP(nkTryStmt, p)
+  getTok(p)
+  colcom(p, result)
+  addSon(result, parseCurlyStmt(p))
+  var b: PNode = nil
+  while true:
+    case p.tok.tokType
+    of tkExcept:
+      b = newNodeP(nkExceptBranch, p)
+      exprList(p, tkCurlyLe, b)
+    of tkFinally:
+      b = newNodeP(nkFinally, p)
+      getTok(p)
+    else: break
+    skipComment(p, b)
+    addSon(b, parseCurlyStmt(p))
+    addSon(result, b)
+    if b.kind == nkFinally: break
+  if b == nil: parMessage(p, errTokenExpected, "except")
+
+proc parseFor(p: var TParser): PNode =
+  #| forStmt = 'for' (identWithPragma ^+ comma) 'in' expr colcom stmt
+  result = newNodeP(nkForStmt, p)
+  getTokNoInd(p)
+  var a = identWithPragma(p)
+  addSon(result, a)
+  while p.tok.tokType == tkComma:
+    getTok(p)
+    optInd(p, a)
+    a = identWithPragma(p)
+    addSon(result, a)
+  eat(p, tkIn)
+  addSon(result, parseExpr(p))
+  colcom(p, result)
+  addSon(result, parseCurlyStmt(p))
+
+proc parseBlock(p: var TParser): PNode =
+  #| blockStmt = 'block' symbol? colcom stmt
+  result = newNodeP(nkBlockStmt, p)
+  getTokNoInd(p)
+  if p.tok.tokType == tkCurlyLe: addSon(result, ast.emptyNode)
+  else: addSon(result, parseSymbol(p))
+  colcom(p, result)
+  addSon(result, parseCurlyStmt(p))
+
+proc parseStaticOrDefer(p: var TParser; k: TNodeKind): PNode =
+  #| staticStmt = 'static' colcom stmt
+  #| deferStmt = 'defer' colcom stmt
+  result = newNodeP(k, p)
+  getTok(p)
+  colcom(p, result)
+  addSon(result, parseCurlyStmt(p))
+
+proc parseAsm(p: var TParser): PNode =
+  #| asmStmt = 'asm' pragma? (STR_LIT | RSTR_LIT | TRIPLE_STR_LIT)
+  result = newNodeP(nkAsmStmt, p)
+  getTokNoInd(p)
+  if p.tok.tokType == tkCurlyDotLe or isAt(p.tok): addSon(result, parsePragma(p))
+  else: addSon(result, ast.emptyNode)
+  case p.tok.tokType
+  of tkStrLit: addSon(result, newStrNodeP(nkStrLit, p.tok.literal, p))
+  of tkRStrLit: addSon(result, newStrNodeP(nkRStrLit, p.tok.literal, p))
+  of tkTripleStrLit: addSon(result,
+                            newStrNodeP(nkTripleStrLit, p.tok.literal, p))
+  else:
+    parMessage(p, errStringLiteralExpected)
+    addSon(result, ast.emptyNode)
+    return
+  getTok(p)
+
+proc parseGenericParam(p: var TParser): PNode =
+  #| genericParam = symbol (comma symbol)* (colon expr)? ('=' optInd expr)?
+  var a: PNode
+  result = newNodeP(nkIdentDefs, p)
+  while true:
+    case p.tok.tokType
+    of tkSymbol, tkAccent:
+      a = parseSymbol(p)
+      if a.kind == nkEmpty: return
+    else: break
+    addSon(result, a)
+    if p.tok.tokType != tkComma: break
+    getTok(p)
+    optInd(p, a)
+  if p.tok.tokType == tkColon:
+    getTok(p)
+    optInd(p, result)
+    addSon(result, parseExpr(p))
+  else:
+    addSon(result, ast.emptyNode)
+  if p.tok.tokType == tkEquals:
+    getTok(p)
+    optInd(p, result)
+    addSon(result, parseExpr(p))
+  else:
+    addSon(result, ast.emptyNode)
+
+proc parseGenericParamList(p: var TParser): PNode =
+  #| genericParamList = '[' optInd
+  #|   genericParam ^* (comma/semicolon) optPar ']'
+  result = newNodeP(nkGenericParams, p)
+  getTok(p)
+  optInd(p, result)
+  while p.tok.tokType in {tkSymbol, tkAccent}:
+    var a = parseGenericParam(p)
+    addSon(result, a)
+    if p.tok.tokType notin {tkComma, tkSemiColon}: break
+    getTok(p)
+    skipComment(p, a)
+  optPar(p)
+  eat(p, tkBracketRi)
+
+proc parsePattern(p: var TParser): PNode =
+  eat(p, tkBracketDotLe)
+  result = parseStmt(p)
+  eat(p, tkBracketDotRi)
+
+proc validInd(p: TParser): bool = p.tok.indent < 0
+
+proc parseRoutine(p: var TParser, kind: TNodeKind): PNode =
+  #| indAndComment = (IND{>} COMMENT)? | COMMENT?
+  #| routine = optInd identVis pattern? genericParamList?
+  #|   paramListColon pragma? ('=' COMMENT? stmt)? indAndComment
+  result = newNodeP(kind, p)
+  getTok(p)
+  optInd(p, result)
+  addSon(result, identVis(p))
+  if p.tok.tokType == tkBracketDotLe and p.validInd:
+    addSon(result, p.parsePattern)
+  else:
+    addSon(result, ast.emptyNode)
+  if p.tok.tokType == tkBracketLe and p.validInd:
+    result.add(p.parseGenericParamList)
+  else:
+    addSon(result, ast.emptyNode)
+  addSon(result, p.parseParamList)
+  if (p.tok.tokType == tkCurlyDotLe or isAt(p.tok)) and p.validInd:
+    addSon(result, p.parsePragma)
+  else:
+    addSon(result, ast.emptyNode)
+  # empty exception tracking:
+  addSon(result, ast.emptyNode)
+  if p.tok.tokType == tkCurlyLe:
+    addSon(result, parseCurlyStmt(p))
+  else:
+    addSon(result, ast.emptyNode)
+  indAndComment(p, result)
+
+proc newCommentStmt(p: var TParser): PNode =
+  #| commentStmt = COMMENT
+  result = newNodeP(nkCommentStmt, p)
+  result.comment = p.tok.literal
+  getTok(p)
+
+type
+  TDefParser = proc (p: var TParser): PNode {.nimcall.}
+
+proc parseSection(p: var TParser, kind: TNodeKind,
+                  defparser: TDefParser): PNode =
+  #| section(p) = COMMENT? p / (IND{>} (p / COMMENT)^+IND{=} DED)
+  result = newNodeP(kind, p)
+  if kind != nkTypeSection: getTok(p)
+  skipComment(p, result)
+  if p.tok.tokType == tkParLe:
+    getTok(p)
+    skipComment(p, result)
+    while true:
+      case p.tok.tokType
+      of tkSymbol, tkAccent, tkParLe:
+        var a = defparser(p)
+        skipComment(p, a)
+        addSon(result, a)
+      of tkComment:
+        var a = newCommentStmt(p)
+        addSon(result, a)
+      of tkParRi: break
+      else:
+        parMessage(p, errIdentifierExpected, p.tok)
+        break
+    eat(p, tkParRi)
+    if result.len == 0: parMessage(p, errIdentifierExpected, p.tok)
+  elif p.tok.tokType in {tkSymbol, tkAccent, tkBracketLe}:
+    # tkBracketLe is allowed for ``var [x, y] = ...`` tuple parsing
+    addSon(result, defparser(p))
+  else:
+    parMessage(p, errIdentifierExpected, p.tok)
+
+proc parseConstant(p: var TParser): PNode =
+  #| constant = identWithPragma (colon typedesc)? '=' optInd expr indAndComment
+  result = newNodeP(nkConstDef, p)
+  addSon(result, identWithPragma(p))
+  if p.tok.tokType == tkColon:
+    getTok(p)
+    optInd(p, result)
+    addSon(result, parseTypeDesc(p))
+  else:
+    addSon(result, ast.emptyNode)
+  eat(p, tkEquals)
+  optInd(p, result)
+  addSon(result, parseExpr(p))
+  indAndComment(p, result)
+
+proc parseEnum(p: var TParser): PNode =
+  #| enum = 'enum' optInd (symbol optInd ('=' optInd expr COMMENT?)? comma?)+
+  result = newNodeP(nkEnumTy, p)
+  getTok(p)
+  addSon(result, ast.emptyNode)
+  optInd(p, result)
+  flexComment(p, result)
+  eat(p, tkCurlyLe)
+  optInd(p, result)
+  while p.tok.tokType notin {tkEof, tkCurlyRi}:
+    var a = parseSymbol(p)
+    if a.kind == nkEmpty: return
+    if p.tok.tokType == tkEquals:
+      getTok(p)
+      optInd(p, a)
+      var b = a
+      a = newNodeP(nkEnumFieldDef, p)
+      addSon(a, b)
+      addSon(a, parseExpr(p))
+      if p.tok.indent < 0:
+        rawSkipComment(p, a)
+    if p.tok.tokType == tkComma:
+      getTok(p)
+      rawSkipComment(p, a)
+    addSon(result, a)
+  eat(p, tkCurlyRi)
+  if result.len <= 1:
+    lexMessageTok(p.lex, errIdentifierExpected, p.tok, prettyTok(p.tok))
+
+proc parseObjectPart(p: var TParser; needsCurly: bool): PNode
+proc parseObjectWhen(p: var TParser): PNode =
+  result = newNodeP(nkRecWhen, p)
+  while true:
+    getTok(p)                 # skip `when`, `elif`
+    var branch = newNodeP(nkElifBranch, p)
+    optInd(p, branch)
+    addSon(branch, parseExpr(p))
+    colcom(p, branch)
+    addSon(branch, parseObjectPart(p, true))
+    flexComment(p, branch)
+    addSon(result, branch)
+    if p.tok.tokType != tkElif: break
+  if p.tok.tokType == tkElse:
+    var branch = newNodeP(nkElse, p)
+    eat(p, tkElse)
+    colcom(p, branch)
+    addSon(branch, parseObjectPart(p, true))
+    flexComment(p, branch)
+    addSon(result, branch)
+
+proc parseObjectCase(p: var TParser): PNode =
+  result = newNodeP(nkRecCase, p)
+  getTokNoInd(p)
+  var a = newNodeP(nkIdentDefs, p)
+  addSon(a, identWithPragma(p))
+  eat(p, tkColon)
+  addSon(a, parseTypeDesc(p))
+  addSon(a, ast.emptyNode)
+  addSon(result, a)
+  eat(p, tkCurlyLe)
+  flexComment(p, result)
+  while true:
+    var b: PNode
+    case p.tok.tokType
+    of tkOf:
+      b = newNodeP(nkOfBranch, p)
+      exprList(p, tkColon, b)
+    of tkElse:
+      b = newNodeP(nkElse, p)
+      getTok(p)
+    else: break
+    colcom(p, b)
+    var fields = parseObjectPart(p, true)
+    if fields.kind == nkEmpty:
+      parMessage(p, errIdentifierExpected, p.tok)
+      fields = newNodeP(nkNilLit, p) # don't break further semantic checking
+    addSon(b, fields)
+    addSon(result, b)
+    if b.kind == nkElse: break
+  eat(p, tkCurlyRi)
+
+proc parseObjectPart(p: var TParser; needsCurly: bool): PNode =
+  if p.tok.tokType == tkCurlyLe:
+    result = newNodeP(nkRecList, p)
+    getTok(p)
+    rawSkipComment(p, result)
+    while true:
+      case p.tok.tokType
+      of tkCase, tkWhen, tkSymbol, tkAccent, tkNil, tkDiscard:
+        addSon(result, parseObjectPart(p, false))
+      of tkCurlyRi: break
+      else:
+        parMessage(p, errIdentifierExpected, p.tok)
+        break
+    eat(p, tkCurlyRi)
+  else:
+    if needsCurly:
+      parMessage(p, errTokenExpected, "{")
+    case p.tok.tokType
+    of tkWhen:
+      result = parseObjectWhen(p)
+    of tkCase:
+      result = parseObjectCase(p)
+    of tkSymbol, tkAccent:
+      result = parseIdentColonEquals(p, {withPragma})
+      if p.tok.indent < 0: rawSkipComment(p, result)
+    of tkNil, tkDiscard:
+      result = newNodeP(nkNilLit, p)
+      getTok(p)
+    else:
+      result = ast.emptyNode
+
+proc parseObject(p: var TParser): PNode =
+  result = newNodeP(nkObjectTy, p)
+  getTok(p)
+  if (p.tok.tokType == tkCurlyDotLe or isAt(p.tok)) and p.validInd:
+    addSon(result, parsePragma(p))
+  else:
+    addSon(result, ast.emptyNode)
+  if p.tok.tokType == tkOf and p.tok.indent < 0:
+    var a = newNodeP(nkOfInherit, p)
+    getTok(p)
+    addSon(a, parseTypeDesc(p))
+    addSon(result, a)
+  else:
+    addSon(result, ast.emptyNode)
+  skipComment(p, result)
+  # an initial IND{>} HAS to follow:
+  addSon(result, parseObjectPart(p, true))
+
+proc parseTypeClassParam(p: var TParser): PNode =
+  if p.tok.tokType in {tkOut, tkVar}:
+    result = newNodeP(nkVarTy, p)
+    getTok(p)
+    result.addSon(p.parseSymbol)
+  else:
+    result = p.parseSymbol
+
+proc parseTypeClass(p: var TParser): PNode =
+  #| typeClassParam = ('var' | 'out')? symbol
+  #| typeClass = typeClassParam ^* ',' (pragma)? ('of' typeDesc ^* ',')?
+  #|               &IND{>} stmt
+  result = newNodeP(nkTypeClassTy, p)
+  getTok(p)
+  var args = newNodeP(nkArgList, p)
+  addSon(result, args)
+  addSon(args, p.parseTypeClassParam)
+  while p.tok.tokType == tkComma:
+    getTok(p)
+    addSon(args, p.parseTypeClassParam)
+  if (p.tok.tokType == tkCurlyDotLe or isAt(p.tok)) and p.validInd:
+    addSon(result, parsePragma(p))
+  else:
+    addSon(result, ast.emptyNode)
+  if p.tok.tokType == tkOf and p.tok.indent < 0:
+    var a = newNodeP(nkOfInherit, p)
+    getTok(p)
+    while true:
+      addSon(a, parseTypeDesc(p))
+      if p.tok.tokType != tkComma: break
+      getTok(p)
+    addSon(result, a)
+  else:
+    addSon(result, ast.emptyNode)
+  if p.tok.tokType == tkComment:
+    skipComment(p, result)
+  addSon(result, parseCurlyStmt(p))
+
+proc parseTypeDef(p: var TParser): PNode =
+  #|
+  #| typeDef = identWithPragmaDot genericParamList? '=' optInd typeDefAux
+  #|             indAndComment?
+  result = newNodeP(nkTypeDef, p)
+  addSon(result, identWithPragma(p, allowDot=true))
+  if p.tok.tokType == tkBracketLe and p.validInd:
+    addSon(result, parseGenericParamList(p))
+  else:
+    addSon(result, ast.emptyNode)
+  if p.tok.tokType == tkEquals:
+    getTok(p)
+    optInd(p, result)
+    addSon(result, parseTypeDefAux(p))
+  else:
+    addSon(result, ast.emptyNode)
+  indAndComment(p, result)    # special extension!
+
+proc parseVarTuple(p: var TParser): PNode =
+  #| varTuple = '(' optInd identWithPragma ^+ comma optPar ')' '=' optInd expr
+  result = newNodeP(nkVarTuple, p)
+  getTok(p)                   # skip '('
+  optInd(p, result)
+  while p.tok.tokType in {tkSymbol, tkAccent}:
+    var a = identWithPragma(p)
+    addSon(result, a)
+    if p.tok.tokType != tkComma: break
+    getTok(p)
+    skipComment(p, a)
+  addSon(result, ast.emptyNode)         # no type desc
+  optPar(p)
+  eat(p, tkBracketRi)
+  eat(p, tkEquals)
+  optInd(p, result)
+  addSon(result, parseExpr(p))
+
+proc parseVariable(p: var TParser): PNode =
+  #| variable = (varTuple / identColonEquals) indAndComment
+  if p.tok.tokType == tkBracketLe: result = parseVarTuple(p)
+  else: result = parseIdentColonEquals(p, {withPragma})
+  indAndComment(p, result)
+
+proc parseBind(p: var TParser, k: TNodeKind): PNode =
+  #| bindStmt = 'bind' optInd qualifiedIdent ^+ comma
+  #| mixinStmt = 'mixin' optInd qualifiedIdent ^+ comma
+  result = newNodeP(k, p)
+  getTok(p)
+  optInd(p, result)
+  while true:
+    var a = qualifiedIdent(p)
+    addSon(result, a)
+    if p.tok.tokType != tkComma: break
+    getTok(p)
+    optInd(p, a)
+
+proc parseStmtPragma(p: var TParser): PNode =
+  result = parsePragma(p)
+  if p.tok.tokType == tkCurlyLe:
+    let a = result
+    result = newNodeI(nkPragmaBlock, a.info)
+    getTok(p)
+    skipComment(p, result)
+    result.add a
+    result.add parseStmt(p)
+    eat(p, tkCurlyRi)
+
+proc simpleStmt(p: var TParser): PNode =
+  case p.tok.tokType
+  of tkReturn: result = parseReturnOrRaise(p, nkReturnStmt)
+  of tkRaise: result = parseReturnOrRaise(p, nkRaiseStmt)
+  of tkYield: result = parseReturnOrRaise(p, nkYieldStmt)
+  of tkDiscard: result = parseReturnOrRaise(p, nkDiscardStmt)
+  of tkBreak: result = parseReturnOrRaise(p, nkBreakStmt)
+  of tkContinue: result = parseReturnOrRaise(p, nkContinueStmt)
+  of tkCurlyDotLe: result = parseStmtPragma(p)
+  of tkImport: result = parseImport(p, nkImportStmt)
+  of tkExport: result = parseImport(p, nkExportStmt)
+  of tkFrom: result = parseFromStmt(p)
+  of tkInclude: result = parseIncludeStmt(p)
+  of tkComment: result = newCommentStmt(p)
+  else:
+    if isExprStart(p): result = parseExprStmt(p)
+    else: result = ast.emptyNode
+  if result.kind notin {nkEmpty, nkCommentStmt}: skipComment(p, result)
+
+proc complexOrSimpleStmt(p: var TParser): PNode =
+  case p.tok.tokType
+  of tkIf: result = parseIfOrWhen(p, nkIfStmt)
+  of tkWhile: result = parseWhile(p)
+  of tkCase: result = parseCase(p)
+  of tkTry: result = parseTry(p)
+  of tkFor: result = parseFor(p)
+  of tkBlock: result = parseBlock(p)
+  of tkStatic: result = parseStaticOrDefer(p, nkStaticStmt)
+  of tkDefer: result = parseStaticOrDefer(p, nkDefer)
+  of tkAsm: result = parseAsm(p)
+  of tkProc: result = parseRoutine(p, nkProcDef)
+  of tkMethod: result = parseRoutine(p, nkMethodDef)
+  of tkIterator: result = parseRoutine(p, nkIteratorDef)
+  of tkMacro: result = parseRoutine(p, nkMacroDef)
+  of tkTemplate: result = parseRoutine(p, nkTemplateDef)
+  of tkConverter: result = parseRoutine(p, nkConverterDef)
+  of tkType:
+    getTok(p)
+    if p.tok.tokType == tkBracketLe:
+      getTok(p)
+      result = newNodeP(nkTypeOfExpr, p)
+      result.addSon(primary(p, pmTypeDesc))
+      eat(p, tkBracketRi)
+      result = parseOperators(p, result, -1, pmNormal)
+    else:
+      result = parseSection(p, nkTypeSection, parseTypeDef)
+  of tkConst: result = parseSection(p, nkConstSection, parseConstant)
+  of tkLet: result = parseSection(p, nkLetSection, parseVariable)
+  of tkWhen: result = parseIfOrWhen(p, nkWhenStmt)
+  of tkVar: result = parseSection(p, nkVarSection, parseVariable)
+  of tkBind: result = parseBind(p, nkBindStmt)
+  of tkMixin: result = parseBind(p, nkMixinStmt)
+  of tkUsing: result = parseSection(p, nkUsingStmt, parseVariable)
+  else: result = simpleStmt(p)
+
+proc parseStmt(p: var TParser): PNode =
+  result = complexOrSimpleStmt(p)
 
 proc parseAll*(p: var TParser): PNode =
-  result = nil
+  ## Parses the rest of the input stream held by the parser into a PNode.
+  result = newNodeP(nkStmtList, p)
+  while p.tok.tokType != tkEof:
+    var a = complexOrSimpleStmt(p)
+    if a.kind != nkEmpty:
+      addSon(result, a)
+    else:
+      parMessage(p, errExprExpected, p.tok)
+      # bugfix: consume a token here to prevent an endless loop:
+      getTok(p)
 
 proc parseTopLevelStmt*(p: var TParser): PNode =
-  result = nil
-
+  ## Implements an iterator which, when called repeatedly, returns the next
+  ## top-level statement or emptyNode if end of stream.
+  result = ast.emptyNode
+  while true:
+    case p.tok.tokType
+    of tkSemiColon: getTok(p)
+    of tkEof: break
+    else:
+      result = complexOrSimpleStmt(p)
+      if result.kind == nkEmpty: parMessage(p, errExprExpected, p.tok)
+      break
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index f4109b26d..e11a8d08b 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -323,7 +323,8 @@ proc processOption(c: PContext, n: PNode): bool =
     of wStacktrace: onOff(c, n, {optStackTrace})
     of wLinetrace: onOff(c, n, {optLineTrace})
     of wDebugger: onOff(c, n, {optEndb})
-    of wProfiler: onOff(c, n, {optProfiler})
+    of wProfiler: onOff(c, n, {optProfiler, optMemTracker})
+    of wMemTracker: onOff(c, n, {optMemTracker})
     of wByRef: onOff(c, n, {optByRef})
     of wDynlib: processDynLib(c, n, nil)
     of wOptimization:
diff --git a/compiler/renderer.nim b/compiler/renderer.nim
index a116a8afe..926e67743 100644
--- a/compiler/renderer.nim
+++ b/compiler/renderer.nim
@@ -54,8 +54,6 @@ proc isKeyword*(i: PIdent): bool =
       (i.id <= ord(tokKeywordHigh) - ord(tkSymbol)):
     result = true
 
-proc isKeyword*(s: string): bool = isKeyword(getIdent(s))
-
 proc renderDefinitionName*(s: PSym, noQuotes = false): string =
   ## Returns the definition name of the symbol.
   ##
@@ -289,8 +287,7 @@ proc lsub(n: PNode): int
 proc litAux(n: PNode, x: BiggestInt, size: int): string =
   proc skip(t: PType): PType =
     result = t
-    while result.kind in {tyGenericInst, tyRange, tyVar, tyDistinct, tyOrdinal,
-                          tyConst, tyMutable}:
+    while result.kind in {tyGenericInst, tyRange, tyVar, tyDistinct, tyOrdinal}:
       result = lastSon(result)
   if n.typ != nil and n.typ.skip.kind in {tyBool, tyEnum}:
     let enumfields = n.typ.skip.n
diff --git a/compiler/rodread.nim b/compiler/rodread.nim
index 679e7ba15..f7e5a0f84 100644
--- a/compiler/rodread.nim
+++ b/compiler/rodread.nim
@@ -142,6 +142,7 @@ type
     methods*: TSymSeq
     origFile: string
     inViewMode: bool
+    cache*: IdentCache
 
   PRodReader* = ref TRodReader
 
@@ -219,7 +220,7 @@ proc decodeNodeLazyBody(r: PRodReader, fInfo: TLineInfo,
       if r.s[r.pos] == '!':
         inc(r.pos)
         var fl = decodeStr(r.s, r.pos)
-        result.ident = getIdent(fl)
+        result.ident = r.cache.getIdent(fl)
       else:
         internalError(result.info, "decodeNode: nkIdent")
     of nkSym:
@@ -401,7 +402,7 @@ proc decodeSym(r: PRodReader, info: TLineInfo): PSym =
     internalError(info, "decodeSym: no id")
   if r.s[r.pos] == '&':
     inc(r.pos)
-    ident = getIdent(decodeStr(r.s, r.pos))
+    ident = r.cache.getIdent(decodeStr(r.s, r.pos))
   else:
     internalError(info, "decodeSym: no ident")
   #echo "decoding: {", ident.s
@@ -519,7 +520,7 @@ proc newStub(r: PRodReader, name: string, id: int): PSym =
   new(result)
   result.kind = skStub
   result.id = id
-  result.name = getIdent(name)
+  result.name = r.cache.getIdent(name)
   result.position = r.readerIndex
   setId(id)                   #MessageOut(result.name.s);
   if debugIds: registerID(result)
@@ -632,7 +633,7 @@ proc processRodFile(r: PRodReader, hash: SecureHash) =
       while r.s[r.pos] > '\x0A':
         w = decodeStr(r.s, r.pos)
         inc(d)
-        if not condsyms.isDefined(getIdent(w)):
+        if not condsyms.isDefined(r.cache.getIdent(w)):
           r.reason = rrDefines #MessageOut('not defined, but should: ' + w);
         if r.s[r.pos] == ' ': inc(r.pos)
       if d != countDefinedSymbols(): r.reason = rrDefines
@@ -707,8 +708,9 @@ proc startsWith(buf: cstring, token: string, pos = 0): bool =
   result = s == token.len
 
 proc newRodReader(modfilename: string, hash: SecureHash,
-                  readerIndex: int): PRodReader =
+                  readerIndex: int; cache: IdentCache): PRodReader =
   new(result)
+  result.cache = cache
   try:
     result.memfile = memfiles.open(modfilename)
   except OSError:
@@ -866,7 +868,7 @@ proc getHash*(fileIdx: int32): SecureHash =
 template growCache*(cache, pos) =
   if cache.len <= pos: cache.setLen(pos+1)
 
-proc checkDep(fileIdx: int32): TReasonForRecompile =
+proc checkDep(fileIdx: int32; cache: IdentCache): TReasonForRecompile =
   assert fileIdx != InvalidFileIDX
   growCache gMods, fileIdx
   if gMods[fileIdx].reason != rrEmpty:
@@ -877,7 +879,7 @@ proc checkDep(fileIdx: int32): TReasonForRecompile =
   gMods[fileIdx].reason = rrNone  # we need to set it here to avoid cycles
   result = rrNone
   var rodfile = toGeneratedFile(filename.withPackageName, RodExt)
-  var r = newRodReader(rodfile, hash, fileIdx)
+  var r = newRodReader(rodfile, hash, fileIdx, cache)
   if r == nil:
     result = (if existsFile(rodfile): rrRodInvalid else: rrRodDoesNotExist)
   else:
@@ -888,10 +890,10 @@ proc checkDep(fileIdx: int32): TReasonForRecompile =
       # NOTE: we need to process the entire module graph so that no ID will
       # be used twice! However, compilation speed does not suffer much from
       # this, since results are cached.
-      var res = checkDep(systemFileIdx)
+      var res = checkDep(systemFileIdx, cache)
       if res != rrNone: result = rrModDeps
       for i in countup(0, high(r.modDeps)):
-        res = checkDep(r.modDeps[i])
+        res = checkDep(r.modDeps[i], cache)
         if res != rrNone:
           result = rrModDeps
           # we cannot break here, because of side-effects of `checkDep`
@@ -904,14 +906,14 @@ proc checkDep(fileIdx: int32): TReasonForRecompile =
   gMods[fileIdx].rd = r
   gMods[fileIdx].reason = result  # now we know better
 
-proc handleSymbolFile*(module: PSym): PRodReader =
+proc handleSymbolFile*(module: PSym; cache: IdentCache): PRodReader =
   let fileIdx = module.fileIdx
   if optSymbolFiles notin gGlobalOptions:
     module.id = getID()
     return nil
   idgen.loadMaxIds(options.gProjectPath / options.gProjectName)
 
-  discard checkDep(fileIdx)
+  discard checkDep(fileIdx, cache)
   if gMods[fileIdx].reason == rrEmpty: internalError("handleSymbolFile")
   result = gMods[fileIdx].rd
   if result != nil:
@@ -1078,7 +1080,7 @@ proc writeType(f: File; t: PType) =
   f.write("]\n")
 
 proc viewFile(rodfile: string) =
-  var r = newRodReader(rodfile, secureHash(""), 0)
+  var r = newRodReader(rodfile, secureHash(""), 0, newIdentCache())
   if r == nil:
     rawMessage(errGenerated, "cannot open file (or maybe wrong version):" &
        rodfile)
diff --git a/compiler/rodwrite.nim b/compiler/rodwrite.nim
index addbdade6..f2422f947 100644
--- a/compiler/rodwrite.nim
+++ b/compiler/rodwrite.nim
@@ -16,6 +16,8 @@ import
   condsyms, ropes, idents, securehash, rodread, passes, importer, idgen,
   rodutils
 
+from modulegraphs import ModuleGraph
+
 type
   TRodWriter = object of TPassContext
     module: PSym
@@ -34,6 +36,7 @@ type
     tstack: TTypeSeq         # a stack of types to process
     files: TStringSeq
     origFile: string
+    cache: IdentCache
 
   PRodWriter = ref TRodWriter
 
@@ -54,7 +57,7 @@ proc fileIdx(w: PRodWriter, filename: string): int =
 template filename*(w: PRodWriter): string =
   w.module.filename
 
-proc newRodWriter(hash: SecureHash, module: PSym): PRodWriter =
+proc newRodWriter(hash: SecureHash, module: PSym; cache: IdentCache): PRodWriter =
   new(result)
   result.sstack = @[]
   result.tstack = @[]
@@ -76,6 +79,7 @@ proc newRodWriter(hash: SecureHash, module: PSym): PRodWriter =
   result.init = ""
   result.origFile = module.info.toFullPath
   result.data = newStringOfCap(12_000)
+  result.cache = cache
 
 proc addModDep(w: PRodWriter, dep: string; info: TLineInfo) =
   if w.modDeps.len != 0: add(w.modDeps, ' ')
@@ -575,15 +579,25 @@ proc process(c: PPassContext, n: PNode): PNode =
     for i in countup(0, sonsLen(n) - 1): discard process(c, n.sons[i])
     #var s = n.sons[namePos].sym
     #addInterfaceSym(w, s)
-  of nkProcDef, nkMethodDef, nkIteratorDef, nkConverterDef,
+  of nkProcDef, nkIteratorDef, nkConverterDef,
       nkTemplateDef, nkMacroDef:
-    var s = n.sons[namePos].sym
+    let s = n.sons[namePos].sym
     if s == nil: internalError(n.info, "rodwrite.process")
     if n.sons[bodyPos] == nil:
       internalError(n.info, "rodwrite.process: body is nil")
     if n.sons[bodyPos].kind != nkEmpty or s.magic != mNone or
         sfForward notin s.flags:
       addInterfaceSym(w, s)
+  of nkMethodDef:
+    let s = n.sons[namePos].sym
+    if s == nil: internalError(n.info, "rodwrite.process")
+    if n.sons[bodyPos] == nil:
+      internalError(n.info, "rodwrite.process: body is nil")
+    if n.sons[bodyPos].kind != nkEmpty or s.magic != mNone or
+        sfForward notin s.flags:
+      pushSym(w, s)
+      processStacks(w, false)
+
   of nkVarSection, nkLetSection, nkConstSection:
     for i in countup(0, sonsLen(n) - 1):
       var a = n.sons[i]
@@ -621,9 +635,9 @@ proc process(c: PPassContext, n: PNode): PNode =
   else:
     discard
 
-proc myOpen(module: PSym): PPassContext =
+proc myOpen(g: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
   if module.id < 0: internalError("rodwrite: module ID not set")
-  var w = newRodWriter(module.fileIdx.getHash, module)
+  var w = newRodWriter(module.fileIdx.getHash, module, cache)
   rawAddInterfaceSym(w, module)
   result = w
 
diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim
index f04cef0ee..1105d3b67 100644
--- a/compiler/scriptconfig.nim
+++ b/compiler/scriptconfig.nim
@@ -11,9 +11,9 @@
 ## language.
 
 import
-  ast, modules, passes, passaux, condsyms,
+  ast, modules, idents, passes, passaux, condsyms,
   options, nimconf, lists, sem, semdata, llstream, vm, vmdef, commands, msgs,
-  os, times, osproc, wordrecg, strtabs
+  os, times, osproc, wordrecg, strtabs, modulegraphs
 
 # we support 'cmpIgnoreStyle' natively for efficiency:
 from strutils import cmpIgnoreStyle, contains
@@ -25,9 +25,9 @@ proc listDirs(a: VmArgs, filter: set[PathComponent]) =
     if kind in filter: result.add path
   setResult(a, result)
 
-proc setupVM*(module: PSym; scriptName: string): PEvalContext =
+proc setupVM*(module: PSym; cache: IdentCache; scriptName: string): PEvalContext =
   # For Nimble we need to export 'setupVM'.
-  result = newCtx(module)
+  result = newCtx(module, cache)
   result.mode = emRepl
   registerAdditionalOps(result)
 
@@ -134,9 +134,11 @@ proc setupVM*(module: PSym; scriptName: string): PEvalContext =
   cbconf selfExe:
     setResult(a, os.getAppFilename())
 
-proc runNimScript*(scriptName: string; freshDefines=true) =
+proc runNimScript*(cache: IdentCache; scriptName: string;
+                   freshDefines=true) =
   passes.gIncludeFile = includeModule
   passes.gImportModule = importModule
+  let graph = newModuleGraph()
   if freshDefines: initDefines()
 
   defineSymbol("nimscript")
@@ -146,15 +148,15 @@ proc runNimScript*(scriptName: string; freshDefines=true) =
 
   appendStr(searchPaths, options.libpath)
 
-  var m = makeModule(scriptName)
+  var m = graph.makeModule(scriptName)
   incl(m.flags, sfMainModule)
-  vm.globalCtx = setupVM(m, scriptName)
+  vm.globalCtx = setupVM(m, cache, scriptName)
 
-  compileSystemModule()
-  discard processModule(m, llStreamOpen(scriptName, fmRead), nil)
+  graph.compileSystemModule(cache)
+  discard graph.processModule(m, llStreamOpen(scriptName, fmRead), nil, cache)
 
   # ensure we load 'system.nim' again for the real non-config stuff!
-  resetAllModulesHard()
+  resetSystemArtifacts()
   vm.globalCtx = nil
   # do not remove the defined symbols
   #initDefines()
diff --git a/compiler/sem.nim b/compiler/sem.nim
index 7768833b3..02c779ef0 100644
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -18,6 +18,8 @@ import
   evaltempl, patterns, parampatterns, sempass2, nimfix.pretty, semmacrosanity,
   semparallel, lowerings, pluginsupport, plugins.active
 
+from modulegraphs import ModuleGraph
+
 when defined(nimfix):
   import nimfix.prettybase
 
@@ -272,7 +274,7 @@ proc tryConstExpr(c: PContext, n: PNode): PNode =
   msgs.gErrorMax = high(int)
 
   try:
-    result = evalConstExpr(c.module, e)
+    result = evalConstExpr(c.module, c.cache, e)
     if result == nil or result.kind == nkEmpty:
       result = nil
     else:
@@ -293,7 +295,7 @@ proc semConstExpr(c: PContext, n: PNode): PNode =
   result = getConstExpr(c.module, e)
   if result == nil:
     #if e.kind == nkEmpty: globalError(n.info, errConstExprExpected)
-    result = evalConstExpr(c.module, e)
+    result = evalConstExpr(c.module, c.cache, e)
     if result == nil or result.kind == nkEmpty:
       if e.info != n.info:
         pushInfoContext(n.info)
@@ -364,7 +366,7 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
 
   #if c.evalContext == nil:
   #  c.evalContext = c.createEvalContext(emStatic)
-  result = evalMacroCall(c.module, n, nOrig, sym)
+  result = evalMacroCall(c.module, c.cache, n, nOrig, sym)
   if efNoSemCheck notin flags:
     result = semAfterMacroCall(c, result, sym, flags)
   popInfoContext()
@@ -398,8 +400,8 @@ proc addCodeForGenerics(c: PContext, n: PNode) =
         addSon(n, prc.ast)
   c.lastGenericIdx = c.generics.len
 
-proc myOpen(module: PSym): PPassContext =
-  var c = newContext(module)
+proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
+  var c = newContext(graph, module, cache)
   if c.p != nil: internalError(module.info, "sem.myOpen")
   c.semConstExpr = semConstExpr
   c.semExpr = semExpr
@@ -428,8 +430,8 @@ proc myOpen(module: PSym): PPassContext =
     gNotes = ForeignPackageNotes
   result = c
 
-proc myOpenCached(module: PSym, rd: PRodReader): PPassContext =
-  result = myOpen(module)
+proc myOpenCached(graph: ModuleGraph; module: PSym; rd: PRodReader): PPassContext =
+  result = myOpen(graph, module, rd.cache)
   for m in items(rd.methods): methodDef(m, true)
 
 proc isImportSystemStmt(n: PNode): bool =
diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim
index 2e925e386..c4116a814 100644
--- a/compiler/semasgn.nim
+++ b/compiler/semasgn.nim
@@ -221,14 +221,15 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) =
       body.add newAsgnStmt(x, call)
   of tyVarargs, tyOpenArray:
     localError(c.info, errGenerated, "cannot copy openArray")
-  of tyFromExpr, tyIter, tyProxy, tyBuiltInTypeClass, tyUserTypeClass,
+  of tyFromExpr, tyProxy, tyBuiltInTypeClass, tyUserTypeClass,
      tyUserTypeClassInst, tyCompositeTypeClass, tyAnd, tyOr, tyNot, tyAnything,
-     tyMutable, tyGenericParam, tyGenericBody, tyNil, tyExpr, tyStmt,
-     tyTypeDesc, tyGenericInvocation, tyBigNum, tyConst, tyForward:
+     tyGenericParam, tyGenericBody, tyNil, tyExpr, tyStmt,
+     tyTypeDesc, tyGenericInvocation, tyForward:
     internalError(c.info, "assignment requested for type: " & typeToString(t))
   of tyOrdinal, tyRange,
      tyGenericInst, tyFieldAccessor, tyStatic, tyVar:
     liftBodyAux(c, lastSon(t), body, x, y)
+  of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("liftBodyAux")
 
 proc newProcType(info: TLineInfo; owner: PSym): PType =
   result = newType(tyProc, owner)
diff --git a/compiler/semcall.nim b/compiler/semcall.nim
index 3037a6ecc..ca9b5effb 100644
--- a/compiler/semcall.nim
+++ b/compiler/semcall.nim
@@ -462,7 +462,7 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym =
   call.add(newIdentNode(fn.name, fn.info))
   for i in 1.. <fn.typ.n.len:
     let param = fn.typ.n.sons[i]
-    let t = skipTypes(param.typ, abstractVar-{tyTypeDesc})
+    let t = skipTypes(param.typ, abstractVar-{tyTypeDesc, tyDistinct})
     if t.kind == tyDistinct or param.typ.kind == tyDistinct: hasDistinct = true
     var x: PType
     if param.typ.kind == tyVar:
diff --git a/compiler/semdata.nim b/compiler/semdata.nim
index 30b6e261d..2fec8c757 100644
--- a/compiler/semdata.nim
+++ b/compiler/semdata.nim
@@ -1,7 +1,7 @@
 #
 #
 #           The Nim Compiler
-#        (c) Copyright 2012 Andreas Rumpf
+#        (c) Copyright 2016 Andreas Rumpf
 #
 #    See the file "copying.txt", included in this
 #    distribution, for details about the copyright.
@@ -13,7 +13,8 @@ import
   strutils, lists, intsets, options, lexer, ast, astalgo, trees, treetab,
   wordrecg,
   ropes, msgs, platform, os, condsyms, idents, renderer, types, extccomp, math,
-  magicsys, nversion, nimsets, parser, times, passes, rodread, vmdef
+  magicsys, nversion, nimsets, parser, times, passes, rodread, vmdef,
+  modulegraphs
 
 type
   TOptionEntry* = object of lists.TListEntry # entries to put on a
@@ -106,7 +107,10 @@ type
     instTypeBoundOp*: proc (c: PContext; dc: PSym; t: PType; info: TLineInfo;
                             op: TTypeAttachedOp; col: int): PSym {.nimcall.}
     selfName*: PIdent
+    cache*: IdentCache
+    graph*: ModuleGraph
     signatures*: TStrTable
+    recursiveDep*: string
 
 proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair =
   result.genericSym = s
@@ -116,29 +120,13 @@ proc filename*(c: PContext): string =
   # the module's filename
   return c.module.filename
 
-proc newContext*(module: PSym): PContext
-
-proc lastOptionEntry*(c: PContext): POptionEntry
-proc newOptionEntry*(): POptionEntry
-proc newLib*(kind: TLibKind): PLib
-proc addToLib*(lib: PLib, sym: PSym)
-proc makePtrType*(c: PContext, baseType: PType): PType
-proc newTypeS*(kind: TTypeKind, c: PContext): PType
-proc fillTypeS*(dest: PType, kind: TTypeKind, c: PContext)
-
 proc scopeDepth*(c: PContext): int {.inline.} =
   result = if c.currentScope != nil: c.currentScope.depthLevel
            else: 0
 
-# owner handling:
-proc getCurrOwner*(): PSym
-proc pushOwner*(owner: PSym)
-proc popOwner*()
-# implementation
-
 var gOwners*: seq[PSym] = @[]
 
-proc getCurrOwner(): PSym =
+proc getCurrOwner*(): PSym =
   # owner stack (used for initializing the
   # owner field of syms)
   # the documentation comment always gets
@@ -146,27 +134,27 @@ proc getCurrOwner(): PSym =
   # BUGFIX: global array is needed!
   result = gOwners[high(gOwners)]
 
-proc pushOwner(owner: PSym) =
+proc pushOwner*(owner: PSym) =
   add(gOwners, owner)
 
-proc popOwner() =
+proc popOwner*() =
   var length = len(gOwners)
   if length > 0: setLen(gOwners, length - 1)
   else: internalError("popOwner")
 
-proc lastOptionEntry(c: PContext): POptionEntry =
+proc lastOptionEntry*(c: PContext): POptionEntry =
   result = POptionEntry(c.optionStack.tail)
 
 proc popProcCon*(c: PContext) {.inline.} = c.p = c.p.next
 
-proc newOptionEntry(): POptionEntry =
+proc newOptionEntry*(): POptionEntry =
   new(result)
   result.options = gOptions
   result.defaultCC = ccDefault
   result.dynlib = nil
   result.notes = gNotes
 
-proc newContext(module: PSym): PContext =
+proc newContext*(graph: ModuleGraph; module: PSym; cache: IdentCache): PContext =
   new(result)
   result.ambiguousSymbols = initIntSet()
   initLinkedList(result.optionStack)
@@ -180,6 +168,8 @@ proc newContext(module: PSym): PContext =
   initStrTable(result.userPragmas)
   result.generics = @[]
   result.unknownIdents = initIntSet()
+  result.cache = cache
+  result.graph = graph
   initStrTable(result.signatures)
 
 
@@ -196,16 +186,19 @@ proc addConverter*(c: PContext, conv: PSym) =
 proc addPattern*(c: PContext, p: PSym) =
   inclSym(c.patterns, p)
 
-proc newLib(kind: TLibKind): PLib =
+proc newLib*(kind: TLibKind): PLib =
   new(result)
   result.kind = kind          #initObjectSet(result.syms)
 
-proc addToLib(lib: PLib, sym: PSym) =
+proc addToLib*(lib: PLib, sym: PSym) =
   #if sym.annex != nil and not isGenericRoutine(sym):
   #  LocalError(sym.info, errInvalidPragma)
   sym.annex = lib
 
-proc makePtrType(c: PContext, baseType: PType): PType =
+proc newTypeS*(kind: TTypeKind, c: PContext): PType =
+  result = newType(kind, getCurrOwner())
+
+proc makePtrType*(c: PContext, baseType: PType): PType =
   result = newTypeS(tyPtr, c)
   addSonSkipIntLit(result, baseType.assertNotNil)
 
@@ -222,7 +215,7 @@ proc makeTypeDesc*(c: PContext, typ: PType): PType =
 
 proc makeTypeSymNode*(c: PContext, typ: PType, info: TLineInfo): PNode =
   let typedesc = makeTypeDesc(c, typ)
-  let sym = newSym(skType, idAnon, getCurrOwner(), info).linkTo(typedesc)
+  let sym = newSym(skType, c.cache.idAnon, getCurrOwner(), info).linkTo(typedesc)
   return newSymNode(sym, info)
 
 proc makeTypeFromExpr*(c: PContext, n: PNode): PType =
@@ -284,9 +277,6 @@ template rangeHasStaticIf*(t: PType): bool =
 template getStaticTypeFromRange*(t: PType): PType =
   t.n[1][0][1].typ
 
-proc newTypeS(kind: TTypeKind, c: PContext): PType =
-  result = newType(kind, getCurrOwner())
-
 proc errorType*(c: PContext): PType =
   ## creates a type representing an error state
   result = newTypeS(tyError, c)
@@ -295,7 +285,7 @@ proc errorNode*(c: PContext, n: PNode): PNode =
   result = newNodeI(nkEmpty, n.info)
   result.typ = errorType(c)
 
-proc fillTypeS(dest: PType, kind: TTypeKind, c: PContext) =
+proc fillTypeS*(dest: PType, kind: TTypeKind, c: PContext) =
   dest.kind = kind
   dest.owner = getCurrOwner()
   dest.size = - 1
diff --git a/compiler/semdestruct.nim b/compiler/semdestruct.nim
index 9ea581f3a..85d106056 100644
--- a/compiler/semdestruct.nim
+++ b/compiler/semdestruct.nim
@@ -124,7 +124,7 @@ proc instantiateDestructor(c: PContext, typ: PType): PType =
   # destructor that must be used for the varialbe.
   # The destructor is either user-defined or automatically
   # generated by the compiler in a member-wise fashion.
-  var t = skipTypes(typ, {tyConst, tyMutable}).skipGenericAlias
+  var t = typ.skipGenericAlias
   let typeHoldingUserDefinition = if t.kind == tyGenericInst: t.base else: t
 
   if typeHoldingUserDefinition.destructor != nil:
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index fbbaaf483..8aaf4f9d8 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -607,12 +607,12 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode =
       call.add(a)
     #echo "NOW evaluating at compile time: ", call.renderTree
     if sfCompileTime in callee.flags:
-      result = evalStaticExpr(c.module, call, c.p.owner)
+      result = evalStaticExpr(c.module, c.cache, call, c.p.owner)
       if result.isNil:
         localError(n.info, errCannotInterpretNodeX, renderTree(call))
       else: result = fixupTypeAfterEval(c, result, n)
     else:
-      result = evalConstExpr(c.module, call)
+      result = evalConstExpr(c.module, c.cache, call)
       if result.isNil: result = n
       else: result = fixupTypeAfterEval(c, result, n)
     #if result != n:
@@ -620,7 +620,7 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode =
 
 proc semStaticExpr(c: PContext, n: PNode): PNode =
   let a = semExpr(c, n.sons[0])
-  result = evalStaticExpr(c.module, a, c.p.owner)
+  result = evalStaticExpr(c.module, c.cache, a, c.p.owner)
   if result.isNil:
     localError(n.info, errCannotInterpretNodeX, renderTree(n))
     result = emptyNode
@@ -695,6 +695,22 @@ proc semBracketedMacro(c: PContext; outer, inner: PNode; s: PSym;
   else: assert(false)
   return
 
+proc afterCallActions(c: PContext; n, orig: PNode, flags: TExprFlags): PNode =
+  result = n
+  let callee = result.sons[0].sym
+  case callee.kind
+  of skMacro: result = semMacroExpr(c, result, orig, callee, flags)
+  of skTemplate: result = semTemplateExpr(c, result, callee, flags)
+  else:
+    semFinishOperands(c, result)
+    activate(c, result)
+    fixAbstractType(c, result)
+    analyseIfAddressTakenInCall(c, result)
+    if callee.magic != mNone:
+      result = magicsAfterOverloadResolution(c, result, flags)
+  if c.inTypeClass == 0:
+    result = evalAtCompileTime(c, result)
+
 proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
   result = nil
   checkMinSonsLen(n, 1)
@@ -773,27 +789,11 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
       # See bug #904 of how to trigger it:
       return result
   #result = afterCallActions(c, result, nOrig, flags)
-  fixAbstractType(c, result)
-  analyseIfAddressTakenInCall(c, result)
-  if result.sons[0].kind == nkSym and result.sons[0].sym.magic != mNone:
-    result = magicsAfterOverloadResolution(c, result, flags)
-  result = evalAtCompileTime(c, result)
-
-proc afterCallActions(c: PContext; n, orig: PNode, flags: TExprFlags): PNode =
-  result = n
-  let callee = result.sons[0].sym
-  case callee.kind
-  of skMacro: result = semMacroExpr(c, result, orig, callee, flags)
-  of skTemplate: result = semTemplateExpr(c, result, callee, flags)
+  if result.sons[0].kind == nkSym:
+    result = afterCallActions(c, result, nOrig, flags)
   else:
-    semFinishOperands(c, result)
-    activate(c, result)
     fixAbstractType(c, result)
     analyseIfAddressTakenInCall(c, result)
-    if callee.magic != mNone:
-      result = magicsAfterOverloadResolution(c, result, flags)
-  if c.inTypeClass == 0:
-    result = evalAtCompileTime(c, result)
 
 proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
   # this seems to be a hotspot in the compiler!
@@ -861,7 +861,7 @@ proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent,
           s = newNodeIT(nkCurly, n.info, setType)
           for j in countup(0, sonsLen(it) - 2): addSon(s, copyTree(it.sons[j]))
           var inExpr = newNodeIT(nkCall, n.info, getSysType(tyBool))
-          addSon(inExpr, newSymNode(ast.opContains, n.info))
+          addSon(inExpr, newSymNode(opContains, n.info))
           addSon(inExpr, s)
           addSon(inExpr, copyTree(r.sons[0]))
           addSon(check, inExpr)
@@ -874,11 +874,11 @@ proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent,
             check = newNodeI(nkCheckedFieldExpr, n.info)
             addSon(check, ast.emptyNode) # make space for access node
           var inExpr = newNodeIT(nkCall, n.info, getSysType(tyBool))
-          addSon(inExpr, newSymNode(ast.opContains, n.info))
+          addSon(inExpr, newSymNode(opContains, n.info))
           addSon(inExpr, s)
           addSon(inExpr, copyTree(r.sons[0]))
           var notExpr = newNodeIT(nkCall, n.info, getSysType(tyBool))
-          addSon(notExpr, newSymNode(ast.opNot, n.info))
+          addSon(notExpr, newSymNode(opNot, n.info))
           addSon(notExpr, inExpr)
           addSon(check, notExpr)
           return
@@ -1551,7 +1551,7 @@ proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym =
   if isCallExpr(n):
     var expandedSym = qualifiedLookUp(c, n[0], {checkUndeclared})
     if expandedSym == nil:
-      localError(n.info, errUndeclaredIdentifier, n[0].renderTree)
+      errorUndeclaredIdentifier(c, n.info, n[0].renderTree)
       return errorSym(c, n[0])
 
     if expandedSym.kind notin {skMacro, skTemplate}:
@@ -1574,9 +1574,9 @@ proc getMagicSym(magic: TMagic): PSym =
   result = newSym(skProc, getIdent($magic), systemModule, gCodegenLineInfo)
   result.magic = magic
 
-proc newAnonSym(kind: TSymKind, info: TLineInfo,
+proc newAnonSym(c: PContext; kind: TSymKind, info: TLineInfo,
                 owner = getCurrOwner()): PSym =
-  result = newSym(kind, idAnon, owner, info)
+  result = newSym(kind, c.cache.idAnon, owner, info)
   result.flags = {sfGenSym}
 
 proc semExpandToAst(c: PContext, n: PNode): PNode =
@@ -1648,7 +1648,7 @@ proc semQuoteAst(c: PContext, n: PNode): PNode =
 
   processQuotations(doBlk.sons[bodyPos], op, quotes, ids)
 
-  doBlk.sons[namePos] = newAnonSym(skTemplate, n.info).newSymNode
+  doBlk.sons[namePos] = newAnonSym(c, skTemplate, n.info).newSymNode
   if ids.len > 0:
     doBlk.sons[paramsPos] = newNodeI(nkFormalParams, n.info)
     doBlk[paramsPos].add getSysSym("stmt").newSymNode # return type
diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim
index b8451865e..ab0ce7c4c 100644
--- a/compiler/semgnrc.nim
+++ b/compiler/semgnrc.nim
@@ -107,7 +107,7 @@ proc lookup(c: PContext, n: PNode, flags: TSemGenericFlags,
   var s = searchInScopes(c, ident).skipAlias(n)
   if s == nil:
     if ident.id notin ctx.toMixin and withinMixin notin flags:
-      localError(n.info, errUndeclaredIdentifier, ident.s)
+      errorUndeclaredIdentifier(c, n.info, ident.s)
   else:
     if withinBind in flags:
       result = symChoice(c, n, s, scClosed)
@@ -195,7 +195,7 @@ proc semGenericStmt(c: PContext, n: PNode,
     if s == nil and withinMixin notin flags and
         fn.kind in {nkIdent, nkAccQuoted} and
         considerQuotedIdent(fn).id notin ctx.toMixin:
-      localError(n.info, errUndeclaredIdentifier, fn.renderTree)
+      errorUndeclaredIdentifier(c, n.info, fn.renderTree)
 
     var first = 0
     var mixinContext = false
diff --git a/compiler/seminst.nim b/compiler/seminst.nim
index d7cad6a2f..e1a65da74 100644
--- a/compiler/seminst.nim
+++ b/compiler/seminst.nim
@@ -58,7 +58,7 @@ iterator instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable): PSym
   for i, a in n.pairs:
     internalAssert a.kind == nkSym
     var q = a.sym
-    if q.typ.kind notin {tyTypeDesc, tyGenericParam, tyStatic, tyIter}+tyTypeClasses:
+    if q.typ.kind notin {tyTypeDesc, tyGenericParam, tyStatic}+tyTypeClasses:
       continue
     let symKind = if q.typ.kind == tyStatic: skConst else: skType
     var s = newSym(symKind, q.name, getCurrOwner(), q.info)
@@ -99,7 +99,8 @@ proc genericCacheGet(genericSym: PSym, entry: TInstantiation;
 
 proc freshGenSyms(n: PNode, owner, orig: PSym, symMap: var TIdTable) =
   # we need to create a fresh set of gensym'ed symbols:
-  if n.kind == nkSym and sfGenSym in n.sym.flags and n.sym.owner == orig:
+  if n.kind == nkSym and sfGenSym in n.sym.flags and
+      (n.sym.owner == orig or n.sym.owner.kind == skPackage):
     let s = n.sym
     var x = PSym(idTableGet(symMap, s))
     if x == nil:
diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim
index 806b00db6..e72172c81 100644
--- a/compiler/semmagic.nim
+++ b/compiler/semmagic.nim
@@ -24,7 +24,7 @@ proc semTypeOf(c: PContext; n: PNode): PNode =
   result = newNodeI(nkTypeOfExpr, n.info)
   let typExpr = semExprWithType(c, n, {efInTypeof})
   result.add typExpr
-  result.typ = makeTypeDesc(c, typExpr.typ.skipTypes({tyTypeDesc, tyIter}))
+  result.typ = makeTypeDesc(c, typExpr.typ.skipTypes({tyTypeDesc}))
 
 type
   SemAsgnMode = enum asgnNormal, noOverloadedSubscript, noOverloadedAsgn
@@ -143,7 +143,7 @@ proc semBindSym(c: PContext, n: PNode): PNode =
     var sc = symChoice(c, id, s, TSymChoiceRule(isMixin.intVal))
     result.add(sc)
   else:
-    localError(n.sons[1].info, errUndeclaredIdentifier, sl.strVal)
+    errorUndeclaredIdentifier(c, n.sons[1].info, sl.strVal)
 
 proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode
 
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index df9b3f69c..8aa8f15c8 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -204,17 +204,22 @@ proc listGcUnsafety(s: PSym; onlyWarning: bool; cycleCheck: var IntSet) =
   let u = s.gcUnsafetyReason
   if u != nil and not cycleCheck.containsOrIncl(u.id):
     let msgKind = if onlyWarning: warnGcUnsafe2 else: errGenerated
-    if u.kind in {skLet, skVar}:
+    case u.kind
+    of skLet, skVar:
       message(s.info, msgKind,
         ("'$#' is not GC-safe as it accesses '$#'" &
         " which is a global using GC'ed memory") % [s.name.s, u.name.s])
-    elif u.kind in routineKinds:
+    of routineKinds:
       # recursive call *always* produces only a warning so the full error
       # message is printed:
       listGcUnsafety(u, true, cycleCheck)
       message(s.info, msgKind,
         "'$#' is not GC-safe as it calls '$#'" %
         [s.name.s, u.name.s])
+    of skParam:
+      message(s.info, msgKind,
+        "'$#' is not GC-safe as it performs an indirect call via '$#'" %
+        [s.name.s, u.name.s])
     else:
       internalAssert u.kind == skUnknown
       message(u.info, msgKind,
@@ -721,7 +726,8 @@ proc track(tracked: PEffects, n: PNode) =
           # and it's not a recursive call:
           if not (a.kind == nkSym and a.sym == tracked.owner):
             markSideEffect(tracked, a)
-    for i in 1 .. <len(n): trackOperand(tracked, n.sons[i], paramType(op, i))
+    if a.kind != nkSym or a.sym.magic != mNBindSym:
+      for i in 1 .. <len(n): trackOperand(tracked, n.sons[i], paramType(op, i))
     if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
       # may not look like an assignment, but it is:
       let arg = n.sons[1]
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index ebcff643f..0c6f6848e 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -524,7 +524,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
       addDefer(c, result, v)
       checkNilable(v)
       if sfCompileTime in v.flags: hasCompileTime = true
-  if hasCompileTime: vm.setupCompileTimeVar(c.module, result)
+  if hasCompileTime: vm.setupCompileTimeVar(c.module, c.cache, result)
 
 proc semConst(c: PContext, n: PNode): PNode =
   result = copyNode(n)
@@ -820,7 +820,7 @@ proc semAllTypeSections(c: PContext; n: PNode): PNode =
           if containsOrIncl(c.includedFiles, f):
             localError(n.info, errRecursiveDependencyX, f.toFilename)
           else:
-            let code = gIncludeFile(c.module, f)
+            let code = gIncludeFile(c.graph, c.module, f, c.cache)
             gatherStmts c, code, result
             excl(c.includedFiles, f)
     of nkStmtList:
@@ -922,7 +922,7 @@ proc semProcAnnotation(c: PContext, prc: PNode;
     if m == nil:
       if key.kind == nkIdent and key.ident.id == ord(wDelegator):
         if considerQuotedIdent(prc.sons[namePos]).s == "()":
-          prc.sons[namePos] = newIdentNode(idDelegator, prc.info)
+          prc.sons[namePos] = newIdentNode(c.cache.idDelegator, prc.info)
           prc.sons[pragmasPos] = copyExcept(n, i)
         else:
           localError(prc.info, errOnlyACallOpCanBeDelegator)
@@ -965,7 +965,7 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode =
   checkSonsLen(n, bodyPos + 1)
   var s: PSym
   if n[namePos].kind != nkSym:
-    s = newSym(skProc, idAnon, getCurrOwner(), n.info)
+    s = newSym(skProc, c.cache.idAnon, getCurrOwner(), n.info)
     s.ast = n
     n.sons[namePos] = newSymNode(s)
   else:
@@ -1159,7 +1159,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
     assert phase == stepRegisterSymbol
 
     if n[namePos].kind == nkEmpty:
-      s = newSym(kind, idAnon, getCurrOwner(), n.info)
+      s = newSym(kind, c.cache.idAnon, getCurrOwner(), n.info)
       incl(s.flags, sfUsed)
       isAnon = true
     else:
@@ -1418,7 +1418,7 @@ proc evalInclude(c: PContext, n: PNode): PNode =
       if containsOrIncl(c.includedFiles, f):
         localError(n.info, errRecursiveDependencyX, f.toFilename)
       else:
-        addSon(result, semStmt(c, gIncludeFile(c.module, f)))
+        addSon(result, semStmt(c, gIncludeFile(c.graph, c.module, f, c.cache)))
         excl(c.includedFiles, f)
 
 proc setLine(n: PNode, info: TLineInfo) =
@@ -1445,7 +1445,7 @@ proc semStaticStmt(c: PContext, n: PNode): PNode =
   #writeStackTrace()
   let a = semStmt(c, n.sons[0])
   n.sons[0] = a
-  evalStaticStmt(c.module, a, c.p.owner)
+  evalStaticStmt(c.module, c.cache, a, c.p.owner)
   result = newNodeI(nkDiscardStmt, n.info, 1)
   result.sons[0] = emptyNode
   when false:
diff --git a/compiler/service.nim b/compiler/service.nim
index 640dd2010..ac04b7860 100644
--- a/compiler/service.nim
+++ b/compiler/service.nim
@@ -11,7 +11,7 @@
 
 import
   times, commands, options, msgs, nimconf,
-  extccomp, strutils, os, platform, parseopt
+  extccomp, strutils, os, platform, parseopt, idents
 
 when useCaas:
   import net
@@ -45,11 +45,11 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
     if optRun notin gGlobalOptions and arguments != "" and options.command.normalize != "run":
       rawMessage(errArgsNeedRunOption, [])
 
-proc serve*(action: proc (){.nimcall.}) =
+proc serve*(cache: IdentCache; action: proc (cache: IdentCache){.nimcall.}) =
   template execute(cmd) =
     curCaasCmd = cmd
     processCmdLine(passCmd2, cmd)
-    action()
+    action(cache)
     gErrorCounter = 0
 
   let typ = getConfigVar("server.type")
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
index df5a76a57..15171874f 100644
--- a/compiler/sigmatch.nim
+++ b/compiler/sigmatch.nim
@@ -549,8 +549,6 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
 
   of tyNil:
     result = f.allowsNil
-  of tyIter:
-    if tfIterator in f.flags: result = typeRel(c, f.base, a.base)
   else: discard
 
 proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} =
@@ -1021,11 +1019,9 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
       var fskip = skippedNone
       let aobj = x.skipToObject(askip)
       let fobj = genericBody.lastSon.skipToObject(fskip)
+      var depth = -1
       if fobj != nil and aobj != nil and askip == fskip:
-        let depth = isObjectSubtype(c, aobj, fobj, f)
-        if depth >= 0:
-          c.inheritancePenalty += depth
-          return if depth == 0: isGeneric else: isSubtype
+        depth = isObjectSubtype(c, aobj, fobj, f)
       result = typeRel(c, genericBody, x)
       if result != isNone:
         # see tests/generics/tgeneric3.nim for an example that triggers this
@@ -1047,6 +1043,11 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
           else:
             put(c, f.sons[i], x)
 
+      if depth >= 0:
+        c.inheritancePenalty += depth
+        # bug #4863: We still need to bind generic alias crap, so
+        # we cannot return immediately:
+        result = if depth == 0: isGeneric else: isSubtype
   of tyAnd:
     considerPreviousT:
       result = isEqual
@@ -1220,13 +1221,6 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
       else:
         result = isNone
 
-  of tyIter:
-    if a.kind == tyIter or
-      (a.kind == tyProc and tfIterator in a.flags):
-      result = typeRel(c, f.base, a.base)
-    else:
-      result = isNone
-
   of tyStmt:
     if aOrig != nil and tfOldSchoolExprStmt notin f.flags:
       put(c, f, aOrig)
@@ -1584,8 +1578,9 @@ proc prepareOperand(c: PContext; formal: PType; a: PNode): PNode =
   elif a.typ.isNil:
     # XXX This is unsound! 'formal' can differ from overloaded routine to
     # overloaded routine!
-    let flags = if formal.kind == tyIter: {efDetermineType, efWantIterator}
-                else: {efDetermineType, efAllowStmt}
+    let flags = {efDetermineType, efAllowStmt}
+                #if formal.kind == tyIter: {efDetermineType, efWantIterator}
+                #else: {efDetermineType, efAllowStmt}
                 #elif formal.kind == tyStmt: {efDetermineType, efWantStmt}
                 #else: {efDetermineType}
     result = c.semOperand(c, a, flags)
diff --git a/compiler/suggest.nim b/compiler/suggest.nim
index 52f00550b..39689099a 100644
--- a/compiler/suggest.nim
+++ b/compiler/suggest.nim
@@ -233,7 +233,7 @@ proc suggestFieldAccess(c: PContext, n: PNode, outputs: var int) =
         # error: no known module name:
         typ = nil
       else:
-        let m = gImportModule(c.module, fullpath.fileInfoIdx)
+        let m = gImportModule(c.graph, c.module, fullpath.fileInfoIdx, c.cache)
         if m == nil: typ = nil
         else:
           for it in items(n.sym.tab):
diff --git a/compiler/syntaxes.nim b/compiler/syntaxes.nim
index 37ea6e2db..4745b1ac7 100644
--- a/compiler/syntaxes.nim
+++ b/compiler/syntaxes.nim
@@ -26,34 +26,11 @@ const
                                               "strip"]
 
 type
-  TParsers*{.final.} = object
+  TParsers* = object
     skin*: TParserKind
     parser*: TParser
 
-
-proc parseFile*(fileIdx: int32): PNode{.procvar.}
-proc openParsers*(p: var TParsers, fileIdx: int32, inputstream: PLLStream)
-proc closeParsers*(p: var TParsers)
-proc parseAll*(p: var TParsers): PNode
-proc parseTopLevelStmt*(p: var TParsers): PNode
-  # implements an iterator. Returns the next top-level statement or nil if end
-  # of stream.
-
-# implementation
-
-proc parseFile(fileIdx: int32): PNode =
-  var
-    p: TParsers
-    f: File
-  let filename = fileIdx.toFullPathConsiderDirty
-  if not open(f, filename):
-    rawMessage(errCannotOpenFile, filename)
-    return
-  openParsers(p, fileIdx, llStreamOpen(f))
-  result = parseAll(p)
-  closeParsers(p)
-
-proc parseAll(p: var TParsers): PNode =
+proc parseAll*(p: var TParsers): PNode =
   case p.skin
   of skinStandard, skinStrongSpaces:
     result = parser.parseAll(p.parser)
@@ -63,7 +40,7 @@ proc parseAll(p: var TParsers): PNode =
     internalError("parser to implement")
     result = ast.emptyNode
 
-proc parseTopLevelStmt(p: var TParsers): PNode =
+proc parseTopLevelStmt*(p: var TParsers): PNode =
   case p.skin
   of skinStandard, skinStrongSpaces:
     result = parser.parseTopLevelStmt(p.parser)
@@ -74,18 +51,18 @@ proc parseTopLevelStmt(p: var TParsers): PNode =
     result = ast.emptyNode
 
 proc utf8Bom(s: string): int =
-  if (s[0] == '\xEF') and (s[1] == '\xBB') and (s[2] == '\xBF'):
+  if s[0] == '\xEF' and s[1] == '\xBB' and s[2] == '\xBF':
     result = 3
   else:
     result = 0
 
 proc containsShebang(s: string, i: int): bool =
-  if (s[i] == '#') and (s[i + 1] == '!'):
+  if s[i] == '#' and s[i+1] == '!':
     var j = i + 2
     while s[j] in Whitespace: inc(j)
     result = s[j] == '/'
 
-proc parsePipe(filename: string, inputStream: PLLStream): PNode =
+proc parsePipe(filename: string, inputStream: PLLStream; cache: IdentCache): PNode =
   result = ast.emptyNode
   var s = llStreamOpen(filename, fmRead)
   if s != nil:
@@ -101,20 +78,20 @@ proc parsePipe(filename: string, inputStream: PLLStream): PNode =
       inc(i, 2)
       while line[i] in Whitespace: inc(i)
       var q: TParser
-      openParser(q, filename, llStreamOpen(substr(line, i)))
+      parser.openParser(q, filename, llStreamOpen(substr(line, i)), cache)
       result = parser.parseAll(q)
-      closeParser(q)
+      parser.closeParser(q)
     llStreamClose(s)
 
 proc getFilter(ident: PIdent): TFilterKind =
   for i in countup(low(TFilterKind), high(TFilterKind)):
-    if identEq(ident, filterNames[i]):
+    if cmpIgnoreStyle(ident.s, filterNames[i]) == 0:
       return i
   result = filtNone
 
 proc getParser(ident: PIdent): TParserKind =
   for i in countup(low(TParserKind), high(TParserKind)):
-    if identEq(ident, parserNames[i]):
+    if cmpIgnoreStyle(ident.s, parserNames[i]) == 0:
       return i
   rawMessage(errInvalidDirectiveX, ident.s)
 
@@ -150,8 +127,7 @@ proc evalPipe(p: var TParsers, n: PNode, filename: string,
               start: PLLStream): PLLStream =
   result = start
   if n.kind == nkEmpty: return
-  if n.kind == nkInfix and n.sons[0].kind == nkIdent and
-      identEq(n.sons[0].ident, "|"):
+  if n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.s == "|":
     for i in countup(1, 2):
       if n.sons[i].kind == nkInfix:
         result = evalPipe(p, n.sons[i], filename, result)
@@ -162,18 +138,31 @@ proc evalPipe(p: var TParsers, n: PNode, filename: string,
   else:
     result = applyFilter(p, n, filename, result)
 
-proc openParsers(p: var TParsers, fileIdx: int32, inputstream: PLLStream) =
+proc openParsers*(p: var TParsers, fileIdx: int32, inputstream: PLLStream;
+                  cache: IdentCache) =
   var s: PLLStream
   p.skin = skinStandard
   let filename = fileIdx.toFullPathConsiderDirty
-  var pipe = parsePipe(filename, inputstream)
+  var pipe = parsePipe(filename, inputstream, cache)
   if pipe != nil: s = evalPipe(p, pipe, filename, inputstream)
   else: s = inputstream
   case p.skin
   of skinStandard, skinBraces, skinEndX:
-    parser.openParser(p.parser, fileIdx, s, false)
+    parser.openParser(p.parser, fileIdx, s, cache, false)
   of skinStrongSpaces:
-    parser.openParser(p.parser, fileIdx, s, true)
+    parser.openParser(p.parser, fileIdx, s, cache, true)
 
-proc closeParsers(p: var TParsers) =
+proc closeParsers*(p: var TParsers) =
   parser.closeParser(p.parser)
+
+proc parseFile*(fileIdx: int32; cache: IdentCache): PNode {.procvar.} =
+  var
+    p: TParsers
+    f: File
+  let filename = fileIdx.toFullPathConsiderDirty
+  if not open(f, filename):
+    rawMessage(errCannotOpenFile, filename)
+    return
+  openParsers(p, fileIdx, llStreamOpen(f), cache)
+  result = parseAll(p)
+  closeParsers(p)
diff --git a/compiler/trees.nim b/compiler/trees.nim
index a629b3834..08a1a8c1f 100644
--- a/compiler/trees.nim
+++ b/compiler/trees.nim
@@ -10,7 +10,7 @@
 # tree helper routines
 
 import
-  ast, astalgo, lexer, msgs, strutils, wordrecg
+  ast, astalgo, lexer, msgs, strutils, wordrecg, idents
 
 proc cyclicTreeAux(n: PNode, visited: var seq[PNode]): bool =
   if n == nil: return
diff --git a/compiler/types.nim b/compiler/types.nim
index 3db0c4507..fc50449ec 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -51,18 +51,14 @@ const
   # TODO: Remove tyTypeDesc from each abstractX and (where necessary)
   # replace with typedescX
   abstractPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyDistinct, tyOrdinal,
-                   tyConst, tyMutable, tyTypeDesc}
-  abstractVar* = {tyVar, tyGenericInst, tyDistinct, tyOrdinal,
-                  tyConst, tyMutable, tyTypeDesc}
-  abstractRange* = {tyGenericInst, tyRange, tyDistinct, tyOrdinal,
-                    tyConst, tyMutable, tyTypeDesc}
-  abstractVarRange* = {tyGenericInst, tyRange, tyVar, tyDistinct, tyOrdinal,
-                       tyConst, tyMutable, tyTypeDesc}
-  abstractInst* = {tyGenericInst, tyDistinct, tyConst, tyMutable, tyOrdinal,
                    tyTypeDesc}
+  abstractVar* = {tyVar, tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc}
+  abstractRange* = {tyGenericInst, tyRange, tyDistinct, tyOrdinal, tyTypeDesc}
+  abstractVarRange* = {tyGenericInst, tyRange, tyVar, tyDistinct, tyOrdinal,
+                       tyTypeDesc}
+  abstractInst* = {tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc}
 
-  skipPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyConst, tyMutable,
-               tyTypeDesc}
+  skipPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyTypeDesc}
   # typedescX is used if we're sure tyTypeDesc should be included (or skipped)
   typedescPtrs* = abstractPtrs + {tyTypeDesc}
   typedescInst* = abstractInst + {tyTypeDesc}
@@ -116,8 +112,7 @@ proc isFloatLit*(t: PType): bool {.inline.} =
 proc isCompatibleToCString(a: PType): bool =
   if a.kind == tyArray:
     if (firstOrd(a.sons[0]) == 0) and
-        (skipTypes(a.sons[0], {tyRange, tyConst,
-                               tyMutable, tyGenericInst}).kind in
+        (skipTypes(a.sons[0], {tyRange, tyGenericInst}).kind in
             {tyInt..tyInt64, tyUInt..tyUInt64}) and
         (a.sons[1].kind == tyChar):
       result = true
@@ -151,13 +146,12 @@ proc isOrdinalType(t: PType): bool =
   const
     # caution: uint, uint64 are no ordinal types!
     baseKinds = {tyChar,tyInt..tyInt64,tyUInt8..tyUInt32,tyBool,tyEnum}
-    parentKinds = {tyRange, tyOrdinal, tyConst, tyMutable, tyGenericInst,
-                   tyDistinct}
+    parentKinds = {tyRange, tyOrdinal, tyGenericInst, 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 {tyConst, tyMutable, tyRange, tyGenericInst}: b = b.sons[0]
+  while b.kind in {tyRange, tyGenericInst}: b = b.sons[0]
   result = b.kind == tyEnum and tfEnumHasHoles in b.flags
 
 proc iterOverTypeAux(marker: var IntSet, t: PType, iter: TTypeIter,
@@ -275,7 +269,7 @@ proc analyseObjectWithTypeFieldAux(t: PType,
       if res == frHeader: result = frHeader
     if result == frNone:
       if isObjectWithTypeFieldPredicate(t): result = frHeader
-  of tyGenericInst, tyDistinct, tyConst, tyMutable:
+  of tyGenericInst, tyDistinct:
     result = analyseObjectWithTypeFieldAux(lastSon(t), marker)
   of tyArray, tyArrayConstr, tyTuple:
     for i in countup(0, sonsLen(t) - 1):
@@ -408,8 +402,8 @@ const
     "int", "int8", "int16", "int32", "int64",
     "float", "float32", "float64", "float128",
     "uint", "uint8", "uint16", "uint32", "uint64",
-    "bignum", "const ",
-    "!", "varargs[$1]", "iter[$1]", "Error Type",
+    "unused0", "unused1",
+    "unused2", "varargs[$1]", "unused", "Error Type",
     "BuiltInTypeClass", "UserTypeClass",
     "UserTypeClassInst", "CompositeTypeClass",
     "and", "or", "not", "any", "static", "TypeFromExpr", "FieldAccessor",
@@ -534,7 +528,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
         add(result, typeToString(t.sons[i]))
         if i < sonsLen(t) - 1: add(result, ", ")
       add(result, ')')
-  of tyPtr, tyRef, tyVar, tyMutable, tyConst:
+  of tyPtr, tyRef, tyVar:
     result = typeToStr[t.kind]
     if t.len >= 2:
       setLen(result, result.len-1)
@@ -572,7 +566,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
       addSep(prag)
       add(prag, "locks: " & $t.lockLevel)
     if len(prag) != 0: add(result, "{." & prag & ".}")
-  of tyVarargs, tyIter:
+  of tyVarargs:
     result = typeToStr[t.kind] % typeToString(t.sons[0])
   else:
     result = typeToStr[t.kind]
@@ -606,7 +600,7 @@ proc firstOrd(t: PType): BiggestInt =
     else:
       assert(t.n.sons[0].kind == nkSym)
       result = t.n.sons[0].sym.position
-  of tyGenericInst, tyDistinct, tyConst, tyMutable, tyTypeDesc, tyFieldAccessor:
+  of tyGenericInst, tyDistinct, tyTypeDesc, tyFieldAccessor:
     result = firstOrd(lastSon(t))
   of tyOrdinal:
     if t.len > 0: result = firstOrd(lastSon(t))
@@ -642,8 +636,7 @@ proc lastOrd(t: PType): BiggestInt =
   of tyEnum:
     assert(t.n.sons[sonsLen(t.n) - 1].kind == nkSym)
     result = t.n.sons[sonsLen(t.n) - 1].sym.position
-  of tyGenericInst, tyDistinct, tyConst, tyMutable,
-     tyTypeDesc, tyFieldAccessor:
+  of tyGenericInst, tyDistinct, tyTypeDesc, tyFieldAccessor:
     result = lastOrd(lastSon(t))
   of tyProxy: result = 0
   of tyOrdinal:
@@ -656,7 +649,7 @@ proc lastOrd(t: PType): BiggestInt =
 proc lengthOrd(t: PType): BiggestInt =
   case t.kind
   of tyInt64, tyInt32, tyInt: result = lastOrd(t)
-  of tyDistinct, tyConst, tyMutable: result = lengthOrd(t.sons[0])
+  of tyDistinct: result = lengthOrd(t.sons[0])
   else:
     let last = lastOrd t
     let first = firstOrd t
@@ -925,7 +918,7 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
 
   case a.kind
   of tyEmpty, tyChar, tyBool, tyNil, tyPointer, tyString, tyCString,
-     tyInt..tyBigNum, tyStmt, tyExpr, tyVoid:
+     tyInt..tyUInt64, tyStmt, tyExpr, tyVoid:
     result = sameFlags(a, b)
   of tyStatic, tyFromExpr:
     result = exprStructuralEquivalent(a.n, b.n) and sameFlags(a, b)
@@ -965,8 +958,7 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
       result = a.sym.position == b.sym.position
   of tyGenericInvocation, tyGenericBody, tySequence,
      tyOpenArray, tySet, tyRef, tyPtr, tyVar, tyArrayConstr,
-     tyArray, tyProc, tyConst, tyMutable, tyVarargs, tyIter,
-     tyOrdinal, tyTypeClasses, tyFieldAccessor:
+     tyArray, tyProc, tyVarargs, tyOrdinal, tyTypeClasses, tyFieldAccessor:
     cycleCheck()
     if a.kind == tyUserTypeClass and a.n != nil: return a.n == b.n
     result = sameChildrenAux(a, b, c) and sameFlags(a, b)
@@ -980,6 +972,7 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
         sameValue(a.n.sons[1], b.n.sons[1])
   of tyGenericInst: discard
   of tyNone: result = false
+  of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("sameFlags")
 
 proc sameBackendType*(x, y: PType): bool =
   var c = initSameTypeClosure()
@@ -1120,7 +1113,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
     result = t
   of tyNil:
     if kind != skConst: result = t
-  of tyString, tyBool, tyChar, tyEnum, tyInt..tyBigNum, tyCString, tyPointer:
+  of tyString, tyBool, tyChar, tyEnum, tyInt..tyUInt64, tyCString, tyPointer:
     result = nil
   of tyOrdinal:
     if kind != skParam: result = t
@@ -1143,7 +1136,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
     else: result = typeAllowedAux(marker, t.lastSon, skVar, flags+{taHeap})
   of tyPtr:
     result = typeAllowedAux(marker, t.lastSon, skVar, flags+{taHeap})
-  of tyArrayConstr, tySet, tyConst, tyMutable, tyIter:
+  of tyArrayConstr, tySet:
     for i in countup(0, sonsLen(t) - 1):
       result = typeAllowedAux(marker, t.sons[i], kind, flags)
       if result != nil: break
@@ -1160,6 +1153,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
     # for now same as error node; we say it's a valid type as it should
     # prevent cascading errors:
     result = nil
+  of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("typeAllowedAux")
 
 proc typeAllowed*(t: PType, kind: TSymKind): PType =
   # returns 'nil' on success and otherwise the part of the type that is
@@ -1250,8 +1244,7 @@ proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt =
     if typ.callConv == ccClosure: result = 2 * ptrSize
     else: result = ptrSize
     a = ptrSize
-  of tyNil, tyCString, tyString, tySequence, tyPtr, tyRef, tyVar, tyOpenArray,
-     tyBigNum:
+  of tyNil, tyCString, tyString, tySequence, tyPtr, tyRef, tyVar, tyOpenArray:
     let base = typ.lastSon
     if base == typ or (base.kind == tyTuple and base.size==szIllegalRecursion):
       result = szIllegalRecursion
@@ -1311,7 +1304,7 @@ proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt =
     if result < 0: return
     if a < maxAlign: a = maxAlign
     result = align(result, a)
-  of tyGenericInst, tyDistinct, tyGenericBody, tyMutable, tyConst, tyIter:
+  of tyGenericInst, tyDistinct, tyGenericBody:
     result = computeSizeAux(lastSon(typ), a)
   of tyTypeDesc:
     result = computeSizeAux(typ.base, a)
@@ -1374,7 +1367,7 @@ proc safeInheritanceDiff*(a, b: PType): int =
   if a.kind == tyError or b.kind == tyError:
     result = -1
   else:
-    result = inheritanceDiff(a, b)
+    result = inheritanceDiff(a.skipTypes(skipPtrs), b.skipTypes(skipPtrs))
 
 proc compatibleEffectsAux(se, re: PNode): bool =
   if re.isNil: return false
diff --git a/compiler/typesrenderer.nim b/compiler/typesrenderer.nim
index 438744b1c..e9c27ac9d 100644
--- a/compiler/typesrenderer.nim
+++ b/compiler/typesrenderer.nim
@@ -37,14 +37,20 @@ proc renderType(n: PNode): string =
   of nkIdent: result = n.ident.s
   of nkSym: result = typeToString(n.sym.typ)
   of nkVarTy:
-    assert len(n) == 1
-    result = renderType(n[0])
+    if n.len == 1:
+      result = renderType(n[0])
+    else:
+      result = "var"
   of nkRefTy:
-    assert len(n) == 1
-    result = "ref." & renderType(n[0])
+    if n.len == 1:
+      result = "ref." & renderType(n[0])
+    else:
+      result = "ref"
   of nkPtrTy:
-    assert len(n) == 1
-    result = "ptr." & renderType(n[0])
+    if n.len == 1:
+      result = "ptr." & renderType(n[0])
+    else:
+      result = "ptr"
   of nkProcTy:
     assert len(n) > 1
     let params = n[0]
diff --git a/compiler/vm.nim b/compiler/vm.nim
index efcc55c59..1bb440c6c 100644
--- a/compiler/vm.nim
+++ b/compiler/vm.nim
@@ -24,6 +24,8 @@ import
 from semfold import leValueConv, ordinalValToString
 from evaltempl import evalTemplate
 
+from modulegraphs import ModuleGraph
+
 when hasFFI:
   import evalffi
 
@@ -76,12 +78,13 @@ proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int; recursionLimit=100) =
     msgWriteln(s)
 
 proc stackTrace(c: PCtx, tos: PStackFrame, pc: int,
-                msg: TMsgKind, arg = "") =
+                msg: TMsgKind, arg = "", n: PNode = nil) =
   msgWriteln("stack trace: (most recent call last)")
   stackTraceAux(c, tos, pc)
   # XXX test if we want 'globalError' for every mode
-  if c.mode == emRepl: globalError(c.debug[pc], msg, arg)
-  else: localError(c.debug[pc], msg, arg)
+  let lineInfo = if n == nil: c.debug[pc] else: n.info
+  if c.mode == emRepl: globalError(lineInfo, msg, arg)
+  else: localError(lineInfo, msg, arg)
 
 proc bailOut(c: PCtx; tos: PStackFrame) =
   stackTrace(c, tos, c.exceptionInstr, errUnhandledExceptionX,
@@ -900,7 +903,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
         if newPc < pc: handleJmpBack()
         #echo "new pc ", newPc, " calling: ", prc.name.s
         var newFrame = PStackFrame(prc: prc, comesFrom: pc, next: tos)
-        newSeq(newFrame.slots, prc.offset)
+        newSeq(newFrame.slots, prc.offset+ord(isClosure))
         if not isEmptyType(prc.typ.sons[0]) or prc.kind == skMacro:
           putIntoReg(newFrame.slots[0], getNullValue(prc.typ.sons[0], prc.info))
         for i in 1 .. rc-1:
@@ -1242,9 +1245,13 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
 
       createStr regs[ra]
       regs[ra].node.strVal = opGorge(regs[rb].node.strVal,
-                                     regs[rc].node.strVal, regs[rd].node.strVal)
+                                     regs[rc].node.strVal, regs[rd].node.strVal,
+                                     c.debug[pc])
     of opcNError:
-      stackTrace(c, tos, pc, errUser, regs[ra].node.strVal)
+      decodeB(rkNode)
+      let a = regs[ra].node
+      let b = regs[rb].node
+      stackTrace(c, tos, pc, errUser, a.strVal, if b.kind == nkNilLit: nil else: b)
     of opcNWarning:
       message(c.debug[pc], warnUser, regs[ra].node.strVal)
     of opcNHint:
@@ -1253,7 +1260,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       decodeB(rkNode)
       # c.debug[pc].line.int - countLines(regs[rb].strVal) ?
       var error: string
-      let ast = parseString(regs[rb].node.strVal, c.debug[pc].toFullPath,
+      let ast = parseString(regs[rb].node.strVal, c.cache, c.debug[pc].toFullPath,
                             c.debug[pc].line.int,
                             proc (info: TLineInfo; msg: TMsgKind; arg: string) =
                               if error.isNil and msg <= msgs.errMax:
@@ -1267,7 +1274,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
     of opcParseStmtToAst:
       decodeB(rkNode)
       var error: string
-      let ast = parseString(regs[rb].node.strVal, c.debug[pc].toFullPath,
+      let ast = parseString(regs[rb].node.strVal, c.cache, c.debug[pc].toFullPath,
                             c.debug[pc].line.int,
                             proc (info: TLineInfo; msg: TMsgKind; arg: string) =
                               if error.isNil and msg <= msgs.errMax:
@@ -1421,7 +1428,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
                  else: regs[rc].node.strVal
       if k < 0 or k > ord(high(TSymKind)):
         internalError(c.debug[pc], "request to create symbol of invalid kind")
-      var sym = newSym(k.TSymKind, name.getIdent, c.module, c.debug[pc])
+      var sym = newSym(k.TSymKind, name.getIdent, c.module.owner, c.debug[pc])
       incl(sym.flags, sfGenSym)
       regs[ra].node = newSymNode(sym)
     of opcTypeTrait:
@@ -1504,20 +1511,20 @@ include vmops
 var
   globalCtx*: PCtx
 
-proc setupGlobalCtx(module: PSym) =
+proc setupGlobalCtx(module: PSym; cache: IdentCache) =
   if globalCtx.isNil:
-    globalCtx = newCtx(module)
+    globalCtx = newCtx(module, cache)
     registerAdditionalOps(globalCtx)
   else:
     refresh(globalCtx, module)
 
-proc myOpen(module: PSym): PPassContext =
+proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
   #var c = newEvalContext(module, emRepl)
   #c.features = {allowCast, allowFFI, allowInfiniteLoops}
   #pushStackFrame(c, newStackFrame())
 
   # XXX produce a new 'globals' environment here:
-  setupGlobalCtx(module)
+  setupGlobalCtx(module, cache)
   result = globalCtx
   when hasFFI:
     globalCtx.features = {allowFFI, allowCast}
@@ -1535,9 +1542,10 @@ proc myProcess(c: PPassContext, n: PNode): PNode =
 
 const evalPass* = makePass(myOpen, nil, myProcess, myProcess)
 
-proc evalConstExprAux(module, prc: PSym, n: PNode, mode: TEvalMode): PNode =
+proc evalConstExprAux(module: PSym; cache: IdentCache; prc: PSym, n: PNode,
+                      mode: TEvalMode): PNode =
   let n = transformExpr(module, n)
-  setupGlobalCtx(module)
+  setupGlobalCtx(module, cache)
   var c = globalCtx
   let oldMode = c.mode
   defer: c.mode = oldMode
@@ -1552,17 +1560,17 @@ proc evalConstExprAux(module, prc: PSym, n: PNode, mode: TEvalMode): PNode =
   result = rawExecute(c, start, tos).regToNode
   if result.info.line < 0: result.info = n.info
 
-proc evalConstExpr*(module: PSym, e: PNode): PNode =
-  result = evalConstExprAux(module, nil, e, emConst)
+proc evalConstExpr*(module: PSym; cache: IdentCache, e: PNode): PNode =
+  result = evalConstExprAux(module, cache, nil, e, emConst)
 
-proc evalStaticExpr*(module: PSym, e: PNode, prc: PSym): PNode =
-  result = evalConstExprAux(module, prc, e, emStaticExpr)
+proc evalStaticExpr*(module: PSym; cache: IdentCache, e: PNode, prc: PSym): PNode =
+  result = evalConstExprAux(module, cache, prc, e, emStaticExpr)
 
-proc evalStaticStmt*(module: PSym, e: PNode, prc: PSym) =
-  discard evalConstExprAux(module, prc, e, emStaticStmt)
+proc evalStaticStmt*(module: PSym; cache: IdentCache, e: PNode, prc: PSym) =
+  discard evalConstExprAux(module, cache, prc, e, emStaticStmt)
 
-proc setupCompileTimeVar*(module: PSym, n: PNode) =
-  discard evalConstExprAux(module, nil, n, emStaticStmt)
+proc setupCompileTimeVar*(module: PSym; cache: IdentCache, n: PNode) =
+  discard evalConstExprAux(module, cache, nil, n, emStaticStmt)
 
 proc setupMacroParam(x: PNode, typ: PType): TFullReg =
   case typ.kind
@@ -1581,7 +1589,8 @@ proc setupMacroParam(x: PNode, typ: PType): TFullReg =
 
 var evalMacroCounter: int
 
-proc evalMacroCall*(module: PSym, n, nOrig: PNode, sym: PSym): PNode =
+proc evalMacroCall*(module: PSym; cache: IdentCache, n, nOrig: PNode,
+                    sym: PSym): PNode =
   # XXX globalError() is ugly here, but I don't know a better solution for now
   inc(evalMacroCounter)
   if evalMacroCounter > 100:
@@ -1594,7 +1603,7 @@ proc evalMacroCall*(module: PSym, n, nOrig: PNode, sym: PSym): PNode =
         n.renderTree,
         $ <n.safeLen, $ <sym.typ.len])
 
-  setupGlobalCtx(module)
+  setupGlobalCtx(module, cache)
   var c = globalCtx
 
   c.callsite = nOrig
diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim
index 83c1dbf43..7fb35e890 100644
--- a/compiler/vmdef.nim
+++ b/compiler/vmdef.nim
@@ -10,7 +10,7 @@
 ## This module contains the type definitions for the new evaluation engine.
 ## An instruction is 1-3 int32s in memory, it is a register based VM.
 
-import ast, passes, msgs, intsets
+import ast, passes, msgs, idents, intsets
 
 const
   byteExcess* = 128 # we use excess-K for immediates
@@ -203,16 +203,18 @@ type
     comesFromHeuristic*: TLineInfo # Heuristic for better macro stack traces
     callbacks*: seq[tuple[key: string, value: VmCallback]]
     errorFlag*: string
+    cache*: IdentCache
 
   TPosition* = distinct int
 
   PEvalContext* = PCtx
 
-proc newCtx*(module: PSym): PCtx =
+proc newCtx*(module: PSym; cache: IdentCache): PCtx =
   PCtx(code: @[], debug: @[],
     globals: newNode(nkStmtListExpr), constants: newNode(nkStmtList), types: @[],
     prc: PProc(blocks: @[]), module: module, loopIterations: MaxLoopIterations,
-    comesFromHeuristic: unknownLineInfo(), callbacks: @[], errorFlag: "")
+    comesFromHeuristic: unknownLineInfo(), callbacks: @[], errorFlag: "",
+    cache: cache)
 
 proc refresh*(c: PCtx, module: PSym) =
   c.module = module
diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim
index b40ca1058..bd6908722 100644
--- a/compiler/vmdeps.nim
+++ b/compiler/vmdeps.nim
@@ -19,7 +19,8 @@ proc readOutput(p: Process): string =
     result.setLen(result.len - "\n".len)
   discard p.waitForExit
 
-proc opGorge*(cmd, input, cache: string): string =
+proc opGorge*(cmd, input, cache: string, info: TLineInfo): string =
+  let workingDir = parentDir(info.toFullPath)
   if cache.len > 0:# and optForceFullMake notin gGlobalOptions:
     let h = secureHash(cmd & "\t" & input & "\t" & cache)
     let filename = options.toGeneratedFile("gorge_" & $h, "txt")
@@ -30,7 +31,8 @@ proc opGorge*(cmd, input, cache: string): string =
       return
     var readSuccessful = false
     try:
-      var p = startProcess(cmd, options={poEvalCommand, poStderrToStdout})
+      var p = startProcess(cmd, workingDir,
+                           options={poEvalCommand, poStderrToStdout})
       if input.len != 0:
         p.inputStream.write(input)
         p.inputStream.close()
@@ -41,7 +43,8 @@ proc opGorge*(cmd, input, cache: string): string =
       if not readSuccessful: result = ""
   else:
     try:
-      var p = startProcess(cmd, options={poEvalCommand, poStderrToStdout})
+      var p = startProcess(cmd, workingDir,
+                           options={poEvalCommand, poStderrToStdout})
       if input.len != 0:
         p.inputStream.write(input)
         p.inputStream.close()
@@ -266,11 +269,7 @@ proc mapTypeToAstX(t: PType; info: TLineInfo;
   of tyUInt16: result = atomicType("uint16", mUint16)
   of tyUInt32: result = atomicType("uint32", mUint32)
   of tyUInt64: result = atomicType("uint64", mUint64)
-  of tyBigNum: result = atomicType("bignum", mNone)
-  of tyConst: result = mapTypeToBracket("const", mNone, t, info)
-  of tyMutable: result = mapTypeToBracket("mutable", mNone, t, info)
   of tyVarargs: result = mapTypeToBracket("varargs", mVarargs, t, info)
-  of tyIter: result = mapTypeToBracket("iter", mNone, t, info)
   of tyProxy: result = atomicType("error", mNone)
   of tyBuiltInTypeClass:
     result = mapTypeToBracket("builtinTypeClass", mNone, t, info)
@@ -292,6 +291,7 @@ proc mapTypeToAstX(t: PType; info: TLineInfo;
       result.add atomicType("static", mNone)
       if t.n != nil:
         result.add t.n.copyTree
+  of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("mapTypeToAstX")
 
 proc opMapTypeToAst*(t: PType; info: TLineInfo): PNode =
   result = mapTypeToAstX(t, info, false, true)
diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim
index 6bfc33f00..ed8f3f338 100644
--- a/compiler/vmgen.nim
+++ b/compiler/vmgen.nim
@@ -1068,7 +1068,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
     else:
       # setter
       unused(n, dest)
-      genUnaryStmt(c, n, opcNError)
+      genBinaryStmt(c, n, opcNError)
   of mNCallSite:
     if dest < 0: dest = c.getTemp(n.typ)
     c.gABC(n, opcCallSite, dest)
diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim
index b5ffd51c2..cf66b6358 100644
--- a/compiler/wordrecg.nim
+++ b/compiler/wordrecg.nim
@@ -13,8 +13,7 @@
 # does not support strings. Without this the code would
 # be slow and unreadable.
 
-import
-  hashes, strutils, idents
+from strutils import cmpIgnoreStyle
 
 # Keywords must be kept sorted and within a range
 
@@ -35,7 +34,7 @@ type
 
     wColon, wColonColon, wEquals, wDot, wDotDot,
     wStar, wMinus,
-    wMagic, wThread, wFinal, wProfiler, wObjChecks,
+    wMagic, wThread, wFinal, wProfiler, wMemTracker, wObjChecks,
     wIntDefine, wStrDefine,
 
     wDestroy,
@@ -122,7 +121,7 @@ const
 
     ":", "::", "=", ".", "..",
     "*", "-",
-    "magic", "thread", "final", "profiler", "objchecks", "intdefine", "strdefine",
+    "magic", "thread", "final", "profiler", "memtracker", "objchecks", "intdefine", "strdefine",
 
     "destroy",
 
@@ -180,17 +179,3 @@ proc findStr*(a: openArray[string], s: string): int =
     if cmpIgnoreStyle(a[i], s) == 0:
       return i
   result = - 1
-
-proc whichKeyword*(id: PIdent): TSpecialWord =
-  if id.id < 0: result = wInvalid
-  else: result = TSpecialWord(id.id)
-
-proc whichKeyword*(id: string): TSpecialWord =
-  result = whichKeyword(getIdent(id))
-
-proc initSpecials() =
-  # initialize the keywords:
-  for s in countup(succ(low(specialWords)), high(specialWords)):
-    getIdent(specialWords[s], hashIgnoreStyle(specialWords[s])).id = ord(s)
-
-initSpecials()
diff --git a/config/nim.cfg b/config/nim.cfg
index 8c8270f3e..b5bcc4053 100644
--- a/config/nim.cfg
+++ b/config/nim.cfg
@@ -177,10 +177,26 @@ clang.options.speed = "-O3"
 clang.options.size = "-Os"
 
 # Configuration for the Visual C/C++ compiler:
-vcc.options.linker = "/DEBUG /Zi /Fd\"$projectName.pdb\" /F33554432" # set the stack size to 8 MB
-vcc.options.debug = "/Zi /Fd\"$projectName.pdb\""
+vcc.exe = "vccexe.exe"
+vcc.linkerexe = "vccexe.exe"
+
+# set the options for specific platforms:
+@if i386:
+vcc.options.always = "--platform:x86 /nologo"
+vcc.options.linker = "--platform:x86 /nologo /DEBUG /Zi /F33554432" # set the stack size to 8 MB
+@elif amd64:
+vcc.options.always = "--platform:amd64 /nologo"
+vcc.options.linker = "--platform:amd64 /nologo /DEBUG /Zi /F33554432" # set the stack size to 8 MB
+@elif arm:
+vcc.options.always = "--platform:arm /nologo"
+vcc.options.linker = "--platform:arm /nologo /DEBUG /Zi /F33554432" # set the stack size to 8 MB
+@else:
 vcc.options.always = "/nologo"
-vcc.options.speed = "/O2 /arch:SSE2"
+vcc.options.linker = "/nologo /DEBUG /Zi /F33554432" # set the stack size to 8 MB
+@end
+
+vcc.options.debug = "/Zi /FS /Od"
+vcc.options.speed = "/O2"
 vcc.options.size = "/O1"
 
 # Configuration for the Tiny C Compiler:
diff --git a/contributors.txt b/contributors.txt
deleted file mode 100644
index d54ec844a..000000000
--- a/contributors.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-Comex
-Eric Doughty-Papassideris
-Simon Hafner
-Keita Haga
-Grzegorz Adam Hankiewicz
-Philippe Lhoste
-Zahary Karadjov
-Mario Ray Mahardhika
-Alexander Mitchell-Robinson (Amrykid)
-Dominik Picheta
-Jonathan Plona
-Alexander Rødseth
diff --git a/doc/advopt.txt b/doc/advopt.txt
index b8980fa9c..991f06397 100644
--- a/doc/advopt.txt
+++ b/doc/advopt.txt
@@ -61,6 +61,9 @@ Advanced options:
   --taintMode:on|off        turn taint mode on|off
   --implicitStatic:on|off   turn implicit compile time evaluation on|off
   --patterns:on|off         turn pattern matching on|off
+  --memTracker:on|off       turn memory tracker on|off
+  --excessiveStackTrace:on|off
+                            stack traces use full file paths
   --skipCfg                 do not read the general configuration file
   --skipUserCfg             do not read the user's configuration file
   --skipParentCfg           do not read the parent dirs' configuration files
diff --git a/doc/astspec.txt b/doc/astspec.txt
index 35eb1727b..f430677af 100644
--- a/doc/astspec.txt
+++ b/doc/astspec.txt
@@ -1184,6 +1184,7 @@ Nim type                     Corresponding AST
 ``proc``                     ``nnkProcTy``
 ``iterator``                 ``nnkIteratorTy``
 ``object``                   ``nnkObjectTy``
+-------------                ---------------------------------------------
 
 Take special care when declaring types as ``proc``. The behavior is similar
 to ``Procedure declaration``, below, but does not treat ``nnkGenericParams``.
diff --git a/doc/basicopt.txt b/doc/basicopt.txt
index 9a1cfd956..eabd531a1 100644
--- a/doc/basicopt.txt
+++ b/doc/basicopt.txt
@@ -5,7 +5,7 @@
 Command:
   //compile, c                compile project with default code generator (C)
   //doc                       generate the documentation for inputfile
-  //doc2                      generate the documentation for the whole project
+  //doc2                      generate the documentation for inputfile
 
 Arguments:
   arguments are passed to the program being run (if --run option is selected)
diff --git a/contributing.rst b/doc/contributing.rst
index 31f04a5e0..31f04a5e0 100644
--- a/contributing.rst
+++ b/doc/contributing.rst
diff --git a/doc/docs.txt b/doc/docs.rst
index 4484784ae..4484784ae 100644
--- a/doc/docs.txt
+++ b/doc/docs.rst
diff --git a/docstyle.rst b/doc/docstyle.rst
index d789b1df9..d789b1df9 100644
--- a/docstyle.rst
+++ b/doc/docstyle.rst
diff --git a/doc/gc.rst b/doc/gc.rst
index 1c8cb9122..bb0088617 100644
--- a/doc/gc.rst
+++ b/doc/gc.rst
@@ -19,8 +19,10 @@ This document describes how the GC works and how to tune it for
 
 The basic algorithm is *Deferred Reference Counting* with cycle detection.
 References on the stack are not counted for better performance (and easier C
-code generation). The GC **never** scans the whole heap but it may scan the
-delta-subgraph of the heap that changed since its last run.
+code generation). Cycle detection is currently done by a simple mark&sweep
+GC that has to scan the full (thread local heap). ``--gc:v2`` replaces this
+with an incremental mark and sweep. That it is not production ready yet,
+however.
 
 
 The GC is only triggered in a memory allocation operation. It is not triggered
@@ -34,17 +36,7 @@ Cycle collector
 ===============
 
 The cycle collector can be en-/disabled independently from the other parts of
-the GC with ``GC_enableMarkAndSweep`` and ``GC_disableMarkAndSweep``. The
-compiler analyses the types for their possibility to build cycles, but often
-it is necessary to help this analysis with the ``acyclic`` pragma (see
-`acyclic <manual.html#acyclic-pragma>`_ for further information).
-
-You can also use the ``acyclic`` pragma for data that is cyclic in reality and
-then break up the cycles explicitly with ``GC_addCycleRoot``. This can be a
-very valuable optimization; the Nim compiler itself relies on this
-optimization trick to improve performance. Note that ``GC_addCycleRoot`` is
-a quick operation; the root is only registered for the next run of the
-cycle collector.
+the GC with ``GC_enableMarkAndSweep`` and ``GC_disableMarkAndSweep``.
 
 
 Realtime support
@@ -55,19 +47,19 @@ defined via ``--define:useRealtimeGC`` (you can put this into your config
 file as well). With this switch the GC supports the following operations:
 
 .. code-block:: nim
-  proc GC_setMaxPause*(MaxPauseInUs: int)
+  proc GC_setMaxPause*(maxPauseInUs: int)
   proc GC_step*(us: int, strongAdvice = false, stackSize = -1)
 
-The unit of the parameters ``MaxPauseInUs`` and ``us`` is microseconds.
+The unit of the parameters ``maxPauseInUs`` and ``us`` is microseconds.
 
 These two procs are the two modus operandi of the realtime GC:
 
 (1) GC_SetMaxPause Mode
 
     You can call ``GC_SetMaxPause`` at program startup and then each triggered
-    GC run tries to not take longer than ``MaxPause`` time. However, it is
+    GC run tries to not take longer than ``maxPause`` time. However, it is
     possible (and common) that the work is nevertheless not evenly distributed
-    as each call to ``new`` can trigger the GC and thus take  ``MaxPause``
+    as each call to ``new`` can trigger the GC and thus take  ``maxPause``
     time.
 
 (2) GC_step Mode
@@ -86,8 +78,8 @@ These two procs are the two modus operandi of the realtime GC:
 These procs provide a "best effort" realtime guarantee; in particular the
 cycle collector is not aware of deadlines yet. Deactivate it to get more
 predictable realtime behaviour. Tests show that a 2ms max pause
-time will be met in almost all cases on modern CPUs unless the cycle collector
-is triggered.
+time will be met in almost all cases on modern CPUs (with the cycle collector
+disabled).
 
 
 Time measurement
diff --git a/doc/intern.txt b/doc/intern.txt
index 05847169f..d0aaa283a 100644
--- a/doc/intern.txt
+++ b/doc/intern.txt
@@ -459,7 +459,7 @@ This should produce roughly this code:
     PEnv = ref object
       x: int # data
 
-  proc anon(y: int, c: PClosure): int =
+  proc anon(y: int, c: PEnv): int =
     return y + c.x
 
   proc add(x: int): tuple[prc, data] =
diff --git a/doc/lib.rst b/doc/lib.rst
index 828889968..8bb602b78 100644
--- a/doc/lib.rst
+++ b/doc/lib.rst
@@ -77,8 +77,9 @@ Collections and algorithms
 * `lists <lists.html>`_
   Nim linked list support. Contains singly and doubly linked lists and
   circular lists ("rings").
-* `queues <queues.html>`_
-  Implementation of a queue. The underlying implementation uses a ``seq``.
+* `deques <deques.html>`_
+  Implementation of a double-ended queue.
+  The underlying implementation uses a ``seq``.
 * `intsets <intsets.html>`_
   Efficient implementation of a set of ints as a sparse bit set.
 * `critbits <critbits.html>`_
diff --git a/doc/manual/exceptions.txt b/doc/manual/exceptions.txt
index d06c13df4..0f1240a4a 100644
--- a/doc/manual/exceptions.txt
+++ b/doc/manual/exceptions.txt
@@ -147,7 +147,7 @@ the ``raise`` statement is the only way to raise an exception.
 If no exception name is given, the current exception is `re-raised`:idx:. The
 `ReraiseError`:idx: exception is raised if there is no exception to
 re-raise. It follows that the ``raise`` statement *always* raises an
-exception (unless a raise hook has been provided).
+exception.
 
 
 Exception hierarchy
diff --git a/doc/manual/types.txt b/doc/manual/types.txt
index 02426e0d9..c81bc042b 100644
--- a/doc/manual/types.txt
+++ b/doc/manual/types.txt
@@ -716,7 +716,8 @@ untraced references are *unsafe*. However for certain low-level operations
 (accessing the hardware) untraced references are unavoidable.
 
 Traced references are declared with the **ref** keyword, untraced references
-are declared with the **ptr** keyword.
+are declared with the **ptr** keyword.  In general, a `ptr T` is implicitly
+convertible to the `pointer` type.
 
 An empty subscript ``[]`` notation can be used to derefer a reference,
 the ``addr`` procedure returns the address of an item. An address is always
diff --git a/doc/nimc.rst b/doc/nimc.rst
index eb1beb549..5d9ed03ab 100644
--- a/doc/nimc.rst
+++ b/doc/nimc.rst
@@ -258,6 +258,10 @@ Define               Effect
 ``ssl``              Enables OpenSSL support for the sockets module.
 ``memProfiler``      Enables memory profiling for the native GC.
 ``uClibc``           Use uClibc instead of libc. (Relevant for Unix-like OSes)
+``checkAbi``         When using types from C headers, add checks that compare
+                     what's in the Nim file with what's in the C header
+                     (requires a C compiler with _Static_assert support, like
+                     any C11 compiler)
 ==================   =========================================================
 
 
diff --git a/doc/nims.rst b/doc/nims.rst
index 7c76efe42..12d86a905 100644
--- a/doc/nims.rst
+++ b/doc/nims.rst
@@ -75,22 +75,8 @@ done:
 Nimble integration
 ==================
 
-A ``project.nims`` file can also be used as an alternative to
-a ``project.nimble`` file to specify the meta information (for example, author,
-description) and dependencies of a Nimble package. This means you can easily
-have platform specific dependencies:
-
-.. code-block:: nim
-
-  version = "1.0"
-  author = "The green goo."
-  description = "Lexer generation and regex implementation for Nim."
-  license = "MIT"
-
-  when defined(windows):
-    requires "oldwinapi >= 1.0"
-  else:
-    requires "gtk2 >= 1.0"
+See the `Nimble readme <https://github.com/nim-lang/nimble#readme>`_
+for more information.
 
 
 
diff --git a/doc/overview.txt b/doc/overview.rst
index 5b41752ae..e01520d7c 100644
--- a/doc/overview.txt
+++ b/doc/overview.rst
@@ -5,5 +5,5 @@ Nim Documentation Overview
 :Author: Andreas Rumpf
 :Version: |nimversion|
 
-.. include:: ../doc/docs.txt
+.. include:: docs.rst
 
diff --git a/icons/koch-amd64-windows-vcc.res b/icons/koch-amd64-windows-vcc.res
new file mode 100644
index 000000000..90d7d1f7a
--- /dev/null
+++ b/icons/koch-amd64-windows-vcc.res
Binary files differdiff --git a/icons/koch-i386-windows-vcc.res b/icons/koch-i386-windows-vcc.res
new file mode 100644
index 000000000..90d7d1f7a
--- /dev/null
+++ b/icons/koch-i386-windows-vcc.res
Binary files differdiff --git a/icons/nim-amd64-windows-vcc.res b/icons/nim-amd64-windows-vcc.res
new file mode 100644
index 000000000..b2d8fc9eb
--- /dev/null
+++ b/icons/nim-amd64-windows-vcc.res
Binary files differdiff --git a/icons/nim-i386-windows-vcc.res b/icons/nim-i386-windows-vcc.res
new file mode 100644
index 000000000..b2d8fc9eb
--- /dev/null
+++ b/icons/nim-i386-windows-vcc.res
Binary files differdiff --git a/install.txt b/install.txt
index 833fbf0fb..6ab562b07 100644
--- a/install.txt
+++ b/install.txt
@@ -33,7 +33,8 @@ is more cumbersome then.
 To complete the installation you should also build Nim's tools like
 ``nimsuggest``, ``nimble`` or ``nimgrep`` via::
 
-  nim e install_tools.nims
+  nim c koch
+  koch tools
 
 Note that these tools should also end up in your ``PATH`` so adding
 ``$your_install_dir/bin/nim`` to your ``PATH`` is preferred over the symlink
@@ -53,10 +54,8 @@ or clang.
 Installation on Windows
 -----------------------
 
-Install Nim by downloading and running the ``nim_$version.exe`` file.
-As default, the ``GCC`` compiler is used that is bundled with this installer.
-You can change the configuration file ``config/nim.cfg`` to use
-another C compiler or change the path to GCC.
+Install Nim by downloading and unzipping the ``nim_$version.zip`` file.
+Run ``finish.exe`` to detect and setup your MingW environment.
 
 Currently, the following C compilers are supported under Windows:
 
@@ -85,4 +84,5 @@ Nimble is Nim's package manager. For the source based installations where you
 added Nim's ``bin`` directory to your ``$PATH`` the easiest way of installing
 Nimble is via::
 
-  nim e install_nimble.nims
+  nim c koch
+  koch nimble
diff --git a/install_nimble.nims b/install_nimble.nims
index 05024c046..6e929f499 100644
--- a/install_nimble.nims
+++ b/install_nimble.nims
@@ -3,6 +3,8 @@ import ospaths
 
 mode = ScriptMode.Verbose
 
+echo "This script is deprecated. Use 'koch nimble' instead."
+
 var id = 0
 while dirExists("nimble" & $id):
   inc id
@@ -20,4 +22,4 @@ try:
   mvFile "nimble" & $id & "/src/nimble".toExe, "bin/nimble".toExe
 except OSError:
   cpFile "nimble" & $id & "/src/nimble".toExe, "bin/nimble".toExe
-  
+
diff --git a/install_tools.nims b/install_tools.nims
index e211ff491..f5f320f78 100644
--- a/install_tools.nims
+++ b/install_tools.nims
@@ -3,6 +3,8 @@ import ospaths
 
 mode = ScriptMode.Verbose
 
+echo "This script is deprecated. Use 'koch tools' instead."
+
 if not dirExists"dist/nimble":
   echo "[Error] This script only works for the tarball."
 else:
@@ -11,8 +13,8 @@ else:
       " dist/nimble/src/nimble.nim"
 
   let nimsugExe = "./bin/nimsuggest".toExe
-  selfExec "c --noNimblePath -p:compiler -o:" & nimsugExe &
+  selfExec "c --noNimblePath -d:release -p:compiler -o:" & nimsugExe &
       " dist/nimsuggest/nimsuggest.nim"
 
   let nimgrepExe = "./bin/nimgrep".toExe
-  selfExec "c -o:./bin/nimgrep tools/nimgrep.nim"
+  selfExec "c -d:release -o:" & nimgrepExe & " tools/nimgrep.nim"
diff --git a/koch.nim b/koch.nim
index 2f936fc4e..d8004b3e6 100644
--- a/koch.nim
+++ b/koch.nim
@@ -15,6 +15,11 @@ when defined(gcc) and defined(windows):
   else:
     {.link: "icons/koch_icon.o".}
 
+when defined(amd64) and defined(windows) and defined(vcc):
+  {.link: "icons/koch-amd64-windows-vcc.res" .}
+when defined(i386) and defined(windows) and defined(vcc):
+  {.link: "icons/koch-i386-windows-vcc.res" .}
+
 import
   os, strutils, parseopt, osproc, streams
 
@@ -35,7 +40,6 @@ Options:
   --help, -h               shows this help and quits
 Possible Commands:
   boot [options]           bootstraps with given command line options
-  finish                   setup PATH and check for a valid GCC installation
   distrohelper [bindir]    helper for distro packagers
   geninstall               generate ./install.sh; Unix only!
   testinstall              test tar.xz package; Unix only! Only for devs!
@@ -50,6 +54,9 @@ Possible Commands:
   tests [options]          run the testsuite
   temp options             creates a temporary compiler for testing
   winrelease               creates a release (for coredevs only)
+  nimble                   builds the Nimble tool
+  tools                    builds Nim related tools
+  pushcsource              push generated C sources to its repo! Only for devs!
 Boot options:
   -d:release               produce a release version of the compiler
   -d:tinyc                 include the Tiny C backend (not supported on Windows)
@@ -89,6 +96,9 @@ proc exec(cmd: string, errorcode: int = QuitFailure, additionalPath = "") =
   if execShellCmd(cmd) != 0: quit("FAILURE", errorcode)
   putEnv("PATH", prevPath)
 
+proc nimexec(cmd: string) =
+  exec findNim() & " " & cmd
+
 proc execCleanPath(cmd: string,
                    additionalPath = ""; errorcode: int = QuitFailure) =
   # simulate a poor man's virtual environment
@@ -145,10 +155,13 @@ const
   compileNimInst = "tools/niminst/niminst"
 
 proc csource(args: string) =
-  exec("$4 cc $1 -r $3 --var:version=$2 --var:mingw=none csource --main:compiler/nim.nim compiler/installer.ini $1" %
-       [args, VersionAsString, compileNimInst, findNim()])
+  nimexec(("cc $1 -r $3 --var:version=$2 --var:mingw=none csource " &
+           "--main:compiler/nim.nim compiler/installer.ini $1") %
+       [args, VersionAsString, compileNimInst])
 
 proc bundleNimbleSrc() =
+  ## bunldeNimbleSrc() bundles a specific Nimble commit with the tarball. We
+  ## always bundle the latest official release.
   if dirExists("dist/nimble/.git"):
     exec("git --git-dir dist/nimble/.git pull")
   else:
@@ -161,41 +174,77 @@ proc bundleNimbleExe() =
   bundleNimbleSrc()
   # now compile Nimble and copy it to $nim/bin for the installer.ini
   # to pick it up:
-  exec(findNim() & " c dist/nimble/src/nimble.nim")
+  nimexec("c dist/nimble/src/nimble.nim")
   copyExe("dist/nimble/src/nimble".exe, "bin/nimble".exe)
 
-proc bundleNimsuggest(buildExe: bool) =
-  if dirExists("dist/nimsuggest/.git"):
-    exec("git --git-dir dist/nimsuggest/.git pull")
+proc buildNimble() =
+  ## buildNimble() builds Nimble for the building via "github". As such, we
+  ## choose the most recent commit of Nimble too.
+  var installDir = "dist/nimble"
+  if dirExists("dist/nimble/.git"):
+    exec("git --git-dir dist/nimble/.git pull")
   else:
-    exec("git clone https://github.com/nim-lang/nimsuggest.git dist/nimsuggest")
+    # if dist/nimble exist, but is not a git repo, don't mess with it:
+    if dirExists(installDir):
+      var id = 0
+      while dirExists("dist/nimble" & $id):
+        inc id
+      installDir = "dist/nimble" & $id
+    exec("git clone https://github.com/nim-lang/nimble.git " & installDir)
+  nimexec("c " & installDir / "src/nimble.nim")
+  copyExe(installDir / "src/nimble".exe, "bin/nimble".exe)
+
+proc bundleNimsuggest(buildExe: bool) =
   if buildExe:
-    exec(findNim() & " c --noNimblePath -p:compiler dist/nimsuggest/nimsuggest.nim")
-    copyExe("dist/nimsuggest/nimsuggest".exe, "bin/nimsuggest".exe)
+    nimexec("c --noNimblePath -d:release -p:compiler tools/nimsuggest/nimsuggest.nim")
+    copyExe("tools/nimsuggest/nimsuggest".exe, "bin/nimsuggest".exe)
+    removeFile("tools/nimsuggest/nimsuggest".exe)
+
+proc bundleWinTools() =
+  nimexec("c tools/finish.nim")
+  copyExe("tools/finish".exe, "finish".exe)
+  removeFile("tools/finish".exe)
+  nimexec("c -o:bin/vccexe.exe tools/vccenv/vccexe")
 
 proc zip(args: string) =
   bundleNimbleSrc()
   bundleNimsuggest(false)
-  exec("$3 cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" %
-       [VersionAsString, compileNimInst, findNim()])
+  bundleWinTools()
+  nimexec("cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" %
+       [VersionAsString, compileNimInst])
   exec("$# --var:version=$# --var:mingw=none --main:compiler/nim.nim zip compiler/installer.ini" %
        ["tools/niminst/niminst".exe, VersionAsString])
 
 proc xz(args: string) =
   bundleNimbleSrc()
   bundleNimsuggest(false)
-  exec("$3 cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" %
-       [VersionAsString, compileNimInst, findNim()])
+  nimexec("cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" %
+       [VersionAsString, compileNimInst])
   exec("$# --var:version=$# --var:mingw=none --main:compiler/nim.nim xz compiler/installer.ini" %
        ["tools" / "niminst" / "niminst".exe, VersionAsString])
 
 proc buildTool(toolname, args: string) =
-  exec("$# cc $# $#" % [findNim(), args, toolname])
+  nimexec("cc $# $#" % [args, toolname])
   copyFile(dest="bin"/ splitFile(toolname).name.exe, source=toolname.exe)
 
+proc buildTools() =
+  let nimsugExe = "bin/nimsuggest".exe
+  nimexec "c --noNimblePath -p:compiler -d:release -o:" & nimsugExe &
+      " tools/nimsuggest/nimsuggest.nim"
+
+  let nimgrepExe = "bin/nimgrep".exe
+  nimexec "c -o:" & nimgrepExe & " tools/nimgrep.nim"
+  if dirExists"dist/nimble":
+    let nimbleExe = "bin/nimble".exe
+    nimexec "c --noNimblePath -p:compiler -o:" & nimbleExe &
+        " dist/nimble/src/nimble.nim"
+  else:
+    buildNimble()
+
 proc nsis(args: string) =
   bundleNimbleExe()
   bundleNimsuggest(true)
+  bundleWinTools()
   # make sure we have generated the niminst executables:
   buildTool("tools/niminst/niminst", args)
   #buildTool("tools/nimgrep", args)
@@ -206,17 +255,21 @@ proc nsis(args: string) =
         " nsis compiler/installer.ini") % [VersionAsString, $(sizeof(pointer)*8)])
 
 proc geninstall(args="") =
-  exec("$# cc -r $# --var:version=$# --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini $#" %
-       [findNim(), compileNimInst, VersionAsString, args])
+  nimexec("cc -r $# --var:version=$# --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini $#" %
+       [compileNimInst, VersionAsString, args])
+
+proc install(args: string) =
+  geninstall()
+  exec("sh ./install.sh $#" % args)
 
 proc web(args: string) =
-  exec("$# js tools/dochack/dochack.nim" % findNim())
-  exec("$# cc -r tools/nimweb.nim $# web/website.ini --putenv:nimversion=$#" %
-       [findNim(), args, VersionAsString])
+  nimexec("js tools/dochack/dochack.nim")
+  nimexec("cc -r tools/nimweb.nim $# web/website.ini --putenv:nimversion=$#" %
+       [args, VersionAsString])
 
 proc website(args: string) =
-  exec("$# cc -r tools/nimweb.nim $# --website web/website.ini --putenv:nimversion=$#" %
-       [findNim(), args, VersionAsString])
+  nimexec("cc -r tools/nimweb.nim $# --website web/website.ini --putenv:nimversion=$#" %
+       [args, VersionAsString])
 
 proc pdf(args="") =
   exec("$# cc -r tools/nimweb.nim $# --pdf web/website.ini --putenv:nimversion=$#" %
@@ -255,7 +308,7 @@ proc boot(args: string) =
   var finalDest = "bin" / "nim".exe
   # default to use the 'c' command:
   let bootOptions = if args.len == 0 or args.startsWith("-"): "c" else: ""
-  let smartNimcache = if "release" in args: "rnimcache" else: "dnimcache"
+  let smartNimcache = if "release" in args: "nimcache/release" else: "nimcache/debug"
 
   copyExe(findStartNim(), 0.thVersion)
   for i in 0..2:
@@ -306,7 +359,6 @@ proc removePattern(pattern: string) =
     removeFile(f)
 
 proc clean(args: string) =
-  if existsFile("koch.dat"): removeFile("koch.dat")
   removePattern("web/*.html")
   removePattern("doc/*.html")
   cleanAux(getCurrentDir())
@@ -320,159 +372,6 @@ proc clean(args: string) =
 proc winRelease() =
   exec(r"call ci\nsis_build.bat " & VersionAsString)
 
-# -------------- post unzip steps ---------------------------------------------
-
-when defined(windows):
-  import registry
-
-  proc askBool(m: string): bool =
-    stdout.write m
-    while true:
-      let answer = stdin.readLine().normalize
-      case answer
-      of "y", "yes":
-        return true
-      of "n", "no":
-        return false
-      else:
-        echo "Please type 'y' or 'n'"
-
-  proc askNumber(m: string; a, b: int): int =
-    stdout.write m
-    stdout.write " [" & $a & ".." & $b & "] "
-    while true:
-      let answer = stdin.readLine()
-      try:
-        result = parseInt answer
-        if result < a or result > b:
-          raise newException(ValueError, "number out of range")
-        break
-      except ValueError:
-        echo "Please type in a number between ", a, " and ", b
-
-  proc patchConfig(mingw: string) =
-    const
-      cfgFile = "config/nim.cfg"
-      lookFor = """#gcc.path = r"$nim\dist\mingw\bin""""
-      replacePattern = """gcc.path = r"$1""""
-    try:
-      let cfg = readFile(cfgFile)
-      let newCfg = cfg.replace(lookFor, replacePattern % mingw)
-      if newCfg == cfg:
-        echo "Could not patch 'config/nim.cfg' [Error]"
-        echo "Reason: patch substring not found:"
-        echo lookFor
-      else:
-        writeFile(cfgFile, newCfg)
-    except IOError:
-      echo "Could not access 'config/nim.cfg' [Error]"
-
-  proc addToPathEnv(e: string) =
-    let p = getUnicodeValue(r"Environment", "Path", HKEY_CURRENT_USER)
-    let x = if e.contains(Whitespace): "\"" & e & "\"" else: e
-    setUnicodeValue(r"Environment", "Path", p & ";" & x, HKEY_CURRENT_USER)
-
-  proc createShortcut(src, dest: string; icon = "") =
-    var cmd = "bin\\makelink.exe \"" & src & "\" \"\" \"" & dest &
-         ".lnk\" \"\" 1 \"" & splitFile(src).dir & "\""
-    if icon.len != 0:
-      cmd.add " \"" & icon & "\" 0"
-    discard execShellCmd(cmd)
-
-  proc createStartMenuEntry() =
-    let appdata = getEnv("APPDATA")
-    if appdata.len == 0: return
-    let dest = appdata & r"\Microsoft\Windows\Start Menu\Programs\Nim-" &
-               VersionAsString
-    if dirExists(dest): return
-    if askBool("Would like to add Nim-" & VersionAsString &
-               " to your start menu? (y/n) "):
-      createDir(dest)
-      createShortcut(getCurrentDir() / "start.bat", dest / "Nim",
-                     getCurrentDir() / r"icons\nim.ico")
-      if fileExists("doc/overview.html"):
-        createShortcut(getCurrentDir() / "doc" / "overview.html",
-                       dest / "Overview")
-      if dirExists(r"dist\aporia-0.4.0"):
-        createShortcut(getCurrentDir() / r"dist\aporia-0.4.0\bin\aporia.exe",
-                       dest / "Aporia")
-
-  proc checkGccArch(mingw: string): bool =
-    let gccExe = mingw / r"gcc.exe"
-    if fileExists(gccExe):
-      try:
-        let arch = execProcess(gccExe, ["-dumpmachine"], nil, {poStdErrToStdOut,
-                                                               poUsePath})
-        when hostCPU == "i386":
-          result = arch.startsWith("i686-")
-        elif hostCPU == "amd64":
-          result = arch.startsWith("x86_64-")
-        else:
-          {.error: "Unknown CPU for Windows.".}
-      except OSError, IOError:
-        result = false
-
-  proc tryDirs(dirs: varargs[string]): string =
-    let bits = $(sizeof(pointer)*8)
-    for d in dirs:
-      if dirExists d:
-        let x = expandFilename(d / "bin")
-        if checkGccArch(x): return x
-      elif dirExists(d & bits):
-        let x = expandFilename((d & bits) / "bin")
-        if checkGccArch(x): return x
-
-proc finish() =
-  when defined(windows):
-    let desiredPath = expandFilename(getCurrentDir() / "bin")
-    let p = getUnicodeValue(r"Environment", "Path",
-      HKEY_CURRENT_USER)
-    var alreadyInPath = false
-    var mingWchoices: seq[string] = @[]
-    for x in p.split(';'):
-      let y = expandFilename(if x[0] == '"' and x[^1] == '"':
-                  substr(x, 1, x.len-2) else: x)
-      if y == desiredPath: alreadyInPath = true
-      if y.toLowerAscii.contains("mingw"):
-        if dirExists(y) and checkGccArch(y):
-          mingWchoices.add y
-
-    if alreadyInPath:
-      echo "bin/nim.exe is already in your PATH [Skipping]"
-    else:
-      if askBool("nim.exe is not in your PATH environment variable.\n" &
-          " Should it be added permanently? (y/n) "):
-        addToPathEnv(desiredPath)
-    if mingWchoices.len == 0:
-      # No mingw in path, so try a few locations:
-      let alternative = tryDirs("dist/mingw", "../mingw", r"C:\mingw")
-      if alternative.len == 0:
-        echo "No MingW found in PATH and no candidate found " &
-            " in the standard locations [Error]"
-      else:
-        if askBool("Found a MingW directory that is not in your PATH.\n" &
-                   alternative &
-                   "\nShould it be added to your PATH permanently? (y/n) "):
-          addToPathEnv(alternative)
-        elif askBool("Do you want to patch Nim's config to use this? (y/n) "):
-          patchConfig(alternative)
-    elif mingWchoices.len == 1:
-      if askBool("MingW installation found at " & mingWchoices[0] & "\n" &
-         "Do you want to patch Nim's config to use this?\n" &
-         "(Not required since it's in your PATH!) (y/n) "):
-        patchConfig(mingWchoices[0])
-    else:
-      echo "Multiple MingW installations found: "
-      for i in 0..high(mingWchoices):
-        echo "[", i, "] ", mingWchoices[i]
-      if askBool("Do you want to patch Nim's config to use one of these? (y/n) "):
-        let idx = askNumber("Which one do you want to use for Nim? ",
-            1, len(mingWchoices))
-        patchConfig(mingWchoices[idx-1])
-    createStartMenuEntry()
-  else:
-    echo("Add ", getCurrentDir(), "/bin to your PATH...")
-
 # -------------- tests --------------------------------------------------------
 
 template `|`(a, b): string = (if a.len > 0: a else: b)
@@ -480,11 +379,10 @@ template `|`(a, b): string = (if a.len > 0: a else: b)
 proc tests(args: string) =
   # we compile the tester with taintMode:on to have a basic
   # taint mode test :-)
-  let nimexe = findNim()
-  exec nimexe & " cc --taintMode:on tests/testament/tester"
+  nimexec "cc --taintMode:on tests/testament/tester"
   # Since tests take a long time (on my machine), and we want to defy Murhpys
   # law - lets make sure the compiler really is freshly compiled!
-  exec nimexe & " c --lib:lib -d:release --opt:speed compiler/nim.nim"
+  nimexec "c --lib:lib -d:release --opt:speed compiler/nim.nim"
   let tester = quoteShell(getCurrentDir() / "tests/testament/tester".exe)
   let success = tryExec tester & " " & (args|"all")
   if not existsEnv("TRAVIS") and not existsEnv("APPVEYOR"):
@@ -501,6 +399,38 @@ proc temp(args: string) =
   copyExe(output, finalDest)
   if args.len > 0: exec(finalDest & " " & args)
 
+proc copyDir(src, dest: string) =
+  for kind, path in walkDir(src, relative=true):
+    case kind
+    of pcDir: copyDir(dest / path, src / path)
+    of pcFile:
+      createDir(dest)
+      copyFile(src / path, dest / path)
+    else: discard
+
+proc pushCsources() =
+  if not dirExists("../csources/.git"):
+    quit "[Error] no csources git repository found"
+  csource("-d:release")
+  let cwd = getCurrentDir()
+  try:
+    copyDir("build/c_code", "../csources/c_code")
+    copyFile("build/build.sh", "../csources/build.sh")
+    copyFile("build/build.bat", "../csources/build.bat")
+    copyFile("build/build64.bat", "../csources/build64.bat")
+    copyFile("build/makefile", "../csources/makefile")
+
+    setCurrentDir("../csources")
+    for kind, path in walkDir("c_code"):
+      if kind == pcDir:
+        exec("git add " & path / "*.c")
+    exec("git commit -am \"updated csources to version " & NimVersion & "\"")
+    exec("git push origin master")
+    exec("git tag -am \"Version $1\" v$1" % NimVersion)
+    exec("git push origin v$1" % NimVersion)
+  finally:
+    setCurrentDir(cwd)
+
 proc showHelp() =
   quit(HelpText % [VersionAsString & spaces(44-len(VersionAsString)),
                    CompileDate, CompileTime], QuitSuccess)
@@ -512,9 +442,9 @@ of cmdLongOption, cmdShortOption: showHelp()
 of cmdArgument:
   case normalize(op.key)
   of "boot": boot(op.cmdLineRest)
-  of "finish": finish()
   of "clean": clean(op.cmdLineRest)
   of "web": web(op.cmdLineRest)
+  of "doc", "docs": web("--onlyDocs " & op.cmdLineRest)
   of "json2": web("--json2 " & op.cmdLineRest)
   of "website": website(op.cmdLineRest & " --googleAnalytics:UA-48159761-1")
   of "web0":
@@ -527,9 +457,13 @@ of cmdArgument:
   of "nsis": nsis(op.cmdLineRest)
   of "geninstall": geninstall(op.cmdLineRest)
   of "distrohelper": geninstall()
+  of "install": install(op.cmdLineRest)
   of "testinstall": testUnixInstall()
   of "test", "tests": tests(op.cmdLineRest)
   of "temp": temp(op.cmdLineRest)
   of "winrelease": winRelease()
+  of "nimble": buildNimble()
+  of "tools": buildTools()
+  of "pushcsource", "pushcsources": pushCsources()
   else: showHelp()
 of cmdEnd: showHelp()
diff --git a/lib/arch/arch.nim b/lib/arch/arch.nim
index c8ae3da1c..0b3df3d3c 100644
--- a/lib/arch/arch.nim
+++ b/lib/arch/arch.nim
@@ -34,7 +34,7 @@ when defined(amd64):
     Reg* {.pure.} = enum
       AX, BX, CX, DX, SI, DI, BP, SP, IP, R8, R9, R10, R11, R12, R13, R14, R15, TOTAL
 
-elif defined(i386):
+elif defined(i386) or defined(nimdoc):
     # identical fastcall calling convention on all x86 OS
     type
       JmpBufReg* {.pure.} = enum
diff --git a/lib/core/macros.nim b/lib/core/macros.nim
index 19452b4a8..b0ef54397 100644
--- a/lib/core/macros.nim
+++ b/lib/core/macros.nim
@@ -78,7 +78,7 @@ type
     nnkBreakState
 
   NimNodeKinds* = set[NimNodeKind]
-  NimTypeKind* = enum
+  NimTypeKind* = enum  # some types are no longer used, see ast.nim
     ntyNone, ntyBool, ntyChar, ntyEmpty,
     ntyArrayConstr, ntyNil, ntyExpr, ntyStmt,
     ntyTypeDesc, ntyGenericInvocation, ntyGenericBody, ntyGenericInst,
@@ -90,9 +90,9 @@ type
     ntyInt8, ntyInt16, ntyInt32, ntyInt64,
     ntyFloat, ntyFloat32, ntyFloat64, ntyFloat128,
     ntyUInt, ntyUInt8, ntyUInt16, ntyUInt32, ntyUInt64,
-    ntyBigNum,
-    ntyConst, ntyMutable, ntyVarargs,
-    ntyIter,
+    ntyUnused0, ntyUnused1, ntyUnused2,
+    ntyVarargs,
+    ntyUnused,
     ntyError,
     ntyBuiltinTypeClass, ntyConcept, ntyConceptInst, ntyComposite,
     ntyAnd, ntyOr, ntyNot
@@ -235,7 +235,7 @@ proc getImpl*(s: NimSym): NimNode {.magic: "GetImpl", noSideEffect.} =
   ## const.
   discard
 
-proc error*(msg: string) {.magic: "NError", benign.}
+proc error*(msg: string, n: NimNode = nil) {.magic: "NError", benign.}
   ## writes an error message at compile time
 
 proc warning*(msg: string) {.magic: "NWarning", benign.}
@@ -377,19 +377,19 @@ proc expectKind*(n: NimNode, k: NimNodeKind) {.compileTime.} =
   ## checks that `n` is of kind `k`. If this is not the case,
   ## compilation aborts with an error message. This is useful for writing
   ## macros that check the AST that is passed to them.
-  if n.kind != k: error("Expected a node of kind " & $k & ", got " & $n.kind)
+  if n.kind != k: error("Expected a node of kind " & $k & ", got " & $n.kind, n)
 
 proc expectMinLen*(n: NimNode, min: int) {.compileTime.} =
   ## checks that `n` has at least `min` children. If this is not the case,
   ## compilation aborts with an error message. This is useful for writing
   ## macros that check its number of arguments.
-  if n.len < min: error("macro expects a node with " & $min & " children")
+  if n.len < min: error("macro expects a node with " & $min & " children", n)
 
 proc expectLen*(n: NimNode, len: int) {.compileTime.} =
   ## checks that `n` has exactly `len` children. If this is not the case,
   ## compilation aborts with an error message. This is useful for writing
   ## macros that check its number of arguments.
-  if n.len != len: error("macro expects a node with " & $len & " children")
+  if n.len != len: error("macro expects a node with " & $len & " children", n)
 
 proc newTree*(kind: NimNodeKind,
               children: varargs[NimNode]): NimNode {.compileTime.} =
@@ -890,6 +890,30 @@ proc boolVal*(n: NimNode): bool {.compileTime, noSideEffect.} =
   if n.kind == nnkIntLit: n.intVal != 0
   else: n == bindSym"true" # hacky solution for now
 
+macro expandMacros*(body: typed): untyped =
+  ## Expands one level of macro - useful for debugging.
+  ## Can be used to inspect what happens when a macro call is expanded,
+  ## without altering its result.
+  ##
+  ## For instance,
+  ##
+  ## .. code-block:: nim
+  ##   import future, macros
+  ##
+  ##   let
+  ##     x = 10
+  ##     y = 20
+  ##   expandMacros:
+  ##     dump(x + y)
+  ##
+  ## will actually dump `x + y`, but at the same time will print at
+  ## compile time the expansion of the ``dump`` macro, which in this
+  ## case is ``debugEcho ["x + y", " = ", x + y]``.
+  template inner(x: untyped): untyped = x
+
+  result = getAst(inner(body))
+  echo result.toStrLit
+
 when not defined(booting):
   template emit*(e: static[string]): untyped {.deprecated.} =
     ## accepts a single string argument and treats it as nim code
diff --git a/lib/impure/nre.nim b/lib/impure/nre.nim
index 557bb0549..626c3fd6b 100644
--- a/lib/impure/nre.nim
+++ b/lib/impure/nre.nim
@@ -431,8 +431,12 @@ proc initRegex(pattern: string, flags: int, study = true): Regex =
     raise SyntaxError(msg: $errorMsg, pos: errOffset, pattern: pattern)
 
   if study:
-    # XXX investigate JIT
-    result.pcreExtra = pcre.study(result.pcreObj, 0x0, addr errorMsg)
+    var options: cint = 0
+    var hasJit: cint
+    if pcre.config(pcre.CONFIG_JIT, addr hasJit) == 0:
+      if hasJit == 1'i32:
+        options = pcre.STUDY_JIT_COMPILE
+    result.pcreExtra = pcre.study(result.pcreObj, options, addr errorMsg)
     if errorMsg != nil:
       raise StudyError(msg: $errorMsg)
 
diff --git a/lib/impure/re.nim b/lib/impure/re.nim
index bd86bcdcf..f7f7c5b25 100644
--- a/lib/impure/re.nim
+++ b/lib/impure/re.nim
@@ -87,7 +87,12 @@ proc re*(s: string, flags = {reExtended, reStudy}): Regex =
   result.h = rawCompile(s, cast[cint](flags - {reStudy}))
   if reStudy in flags:
     var msg: cstring
-    result.e = pcre.study(result.h, 0, addr msg)
+    var options: cint = 0
+    var hasJit: cint
+    if pcre.config(pcre.CONFIG_JIT, addr hasJit) == 0:
+      if hasJit == 1'i32:
+        options = pcre.STUDY_JIT_COMPILE
+    result.e = pcre.study(result.h, options, addr msg)
     if not isNil(msg): raiseInvalidRegex($msg)
 
 proc matchOrFind(s: string, pattern: Regex, matches: var openArray[string],
@@ -214,7 +219,7 @@ proc find*(s: string, pattern: Regex, start = 0): int =
   var
     rtarray = initRtArray[cint](3)
     rawMatches = rtarray.getRawData
-    res = pcre.exec(pattern.h, nil, s, len(s).cint, start.cint, 0'i32,
+    res = pcre.exec(pattern.h, pattern.e, s, len(s).cint, start.cint, 0'i32,
       cast[ptr cint](rawMatches), 3)
   if res < 0'i32: return res
   return rawMatches[0]
diff --git a/lib/js/jsconsole.nim b/lib/js/jsconsole.nim
new file mode 100644
index 000000000..d9ced95f0
--- /dev/null
+++ b/lib/js/jsconsole.nim
@@ -0,0 +1,44 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2012 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Wrapper for the `console` object for the `JavaScript backend
+## <backends.html#the-javascript-target>`_.
+
+when not defined(js) and not defined(Nimdoc):
+  {.error: "This module only works on the JavaScript platform".}
+
+import macros
+
+type Console* {.importc.} = ref object of RootObj
+
+proc convertToConsoleLoggable*[T](v: T): RootRef {.importcpp: "#".}
+template convertToConsoleLoggable*(v: string): RootRef = cast[RootRef](cstring(v))
+
+proc logImpl(console: Console) {.importcpp: "log", varargs.}
+proc debugImpl(console: Console) {.importcpp: "debug", varargs.}
+proc infoImpl(console: Console) {.importcpp: "info", varargs.}
+proc errorImpl(console: Console) {.importcpp: "error", varargs.}
+
+proc makeConsoleCall(console: NimNode, procName: NimNode, args: NimNode): NimNode =
+  result = newCall(procName, console)
+  for c in args: result.add(c)
+
+macro log*(console: Console, args: varargs[RootRef, convertToConsoleLoggable]): untyped =
+  makeConsoleCall(console, bindSym "logImpl", args)
+
+macro debug*(console: Console, args: varargs[RootRef, convertToConsoleLoggable]): untyped =
+  makeConsoleCall(console, bindSym "debugImpl", args)
+
+macro info*(console: Console, args: varargs[RootRef, convertToConsoleLoggable]): untyped =
+  makeConsoleCall(console, bindSym "infoImpl", args)
+
+macro error*(console: Console, args: varargs[RootRef, convertToConsoleLoggable]): untyped =
+  makeConsoleCall(console, bindSym "errorImpl", args)
+
+var console* {.importc, nodecl.}: Console
\ No newline at end of file
diff --git a/lib/nimbase.h b/lib/nimbase.h
index 52de60969..818bff462 100644
--- a/lib/nimbase.h
+++ b/lib/nimbase.h
@@ -459,3 +459,7 @@ typedef int Nim_and_C_compiler_disagree_on_target_architecture[sizeof(NI) == siz
 #elif defined(__FreeBSD__)
 #  include <sys/types.h>
 #endif
+
+/* Compile with -d:checkAbi and a sufficiently C11:ish compiler to enable */
+#define NIM_CHECK_SIZE(typ, sz) \
+  _Static_assert(sizeof(typ) == sz, "Nim & C disagree on type size")
diff --git a/lib/packages/docutils/highlite.nim b/lib/packages/docutils/highlite.nim
index 9de25f82b..06b90768c 100644
--- a/lib/packages/docutils/highlite.nim
+++ b/lib/packages/docutils/highlite.nim
@@ -242,14 +242,17 @@ proc nimNextToken(g: var GeneralTokenizer) =
       inc(pos)
       case g.buf[pos]
       of 'b', 'B':
+        g.kind = gtBinNumber
         inc(pos)
         while g.buf[pos] in binChars: inc(pos)
         pos = nimNumberPostfix(g, pos)
       of 'x', 'X':
+        g.kind = gtHexNumber
         inc(pos)
         while g.buf[pos] in hexChars: inc(pos)
         pos = nimNumberPostfix(g, pos)
       of 'o', 'O':
+        g.kind = gtOctNumber
         inc(pos)
         while g.buf[pos] in octChars: inc(pos)
         pos = nimNumberPostfix(g, pos)
@@ -700,8 +703,8 @@ proc yamlNextToken(g: var GeneralTokenizer) =
       g.state = gtLongStringLit
   elif g.state == gtLongStringLit:
     # beware, this is the only token where we actually have to parse
-    # indentation. 
-    
+    # indentation.
+
     g.kind = gtLongStringLit
     # first, we have to find the parent indentation of the block scalar, so that
     # we know when to stop
@@ -738,20 +741,20 @@ proc yamlNextToken(g: var GeneralTokenizer) =
     # because lookbehind was at newline char when calculating indentation, we're
     # off by one. fix that. top level's parent will have indentation of -1.
     let parentIndentation = indentation - 1
-    
+
     # find first content
     while g.buf[pos] in {' ', '\x0A', '\x0D'}:
       if g.buf[pos] == ' ': inc(indentation)
       else: indentation = 0
       inc(pos)
     var minIndentation = indentation
-    
+
     # for stupid edge cases, we must check whether an explicit indentation depth
     # is given at the header.
     while g.buf[headerStart] in {'>', '|', '+', '-'}: inc(headerStart)
     if g.buf[headerStart] in {'0'..'9'}:
       minIndentation = min(minIndentation, ord(g.buf[headerStart]) - ord('0'))
-    
+
     # process content lines
     while indentation > parentIndentation and g.buf[pos] != '\0':
       if (indentation < minIndentation and g.buf[pos] == '#') or
@@ -766,7 +769,7 @@ proc yamlNextToken(g: var GeneralTokenizer) =
         if g.buf[pos] == ' ': inc(indentation)
         else: indentation = 0
         inc(pos)
-    
+
     g.state = gtOther
   elif g.state == gtOther:
     # gtOther means 'inside YAML document'
diff --git a/lib/posix/kqueue.nim b/lib/posix/kqueue.nim
index 5c67d621e..19d661490 100644
--- a/lib/posix/kqueue.nim
+++ b/lib/posix/kqueue.nim
@@ -123,7 +123,7 @@ when defined(macosx) or defined(freebsd):
     NOTE_USECONDS*   = 0x00000004'u32 ## data is microseconds
     NOTE_NSECONDS*   = 0x00000008'u32 ## data is nanoseconds
 else:
-  # NetBSD and OpenBSD doesnt support NOTE_{TIME} constants, but
+  # NetBSD and OpenBSD doesn't support NOTE_{TIME} constants, but
   # support EVFILT_TIMER with granularity of milliseconds.
   const
     NOTE_MSECONDS*   = 0x00000000'u32
diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim
index 8336da1fb..01088c2e7 100644
--- a/lib/pure/asyncdispatch.nim
+++ b/lib/pure/asyncdispatch.nim
@@ -11,7 +11,7 @@ include "system/inclrtl"
 
 import os, oids, tables, strutils, times, heapqueue
 
-import nativesockets, net, queues
+import nativesockets, net, deques
 
 export Port, SocketFlag
 
@@ -132,7 +132,7 @@ export Port, SocketFlag
 ##        # Handle exception
 ##
 ## Unfortunately the semantics of the try statement may not always be correct,
-## and occassionally the compilation may fail altogether.
+## and occasionally the compilation may fail altogether.
 ## As such it is better to use the former style when possible.
 ##
 ## Discarding futures
@@ -164,7 +164,7 @@ include includes/asyncfutures
 type
   PDispatcherBase = ref object of RootRef
     timers: HeapQueue[tuple[finishAt: float, fut: Future[void]]]
-    callbacks: Queue[proc ()]
+    callbacks: Deque[proc ()]
 
 proc processTimers(p: PDispatcherBase) {.inline.} =
   while p.timers.len > 0 and epochTime() >= p.timers[0].finishAt:
@@ -172,7 +172,7 @@ proc processTimers(p: PDispatcherBase) {.inline.} =
 
 proc processPendingCallbacks(p: PDispatcherBase) =
   while p.callbacks.len > 0:
-    var cb = p.callbacks.dequeue()
+    var cb = p.callbacks.popFirst()
     cb()
 
 proc adjustedTimeout(p: PDispatcherBase, timeout: int): int {.inline.} =
@@ -230,7 +230,7 @@ when defined(windows) or defined(nimdoc):
     result.ioPort = createIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1)
     result.handles = initSet[AsyncFD]()
     result.timers.newHeapQueue()
-    result.callbacks = initQueue[proc ()](64)
+    result.callbacks = initDeque[proc ()](64)
 
   var gDisp{.threadvar.}: PDispatcher ## Global dispatcher
   proc getGlobalDispatcher*(): PDispatcher =
@@ -987,7 +987,7 @@ else:
     new result
     result.selector = newSelector()
     result.timers.newHeapQueue()
-    result.callbacks = initQueue[proc ()](64)
+    result.callbacks = initDeque[proc ()](64)
 
   var gDisp{.threadvar.}: PDispatcher ## Global dispatcher
   proc getGlobalDispatcher*(): PDispatcher =
@@ -1043,6 +1043,26 @@ else:
     p.selector[fd.SocketHandle].data.PData.writeCBs.add(cb)
     update(fd, p.selector[fd.SocketHandle].events + {EvWrite})
 
+  template processCallbacks(callbacks: expr) =
+    # Callback may add items to ``callbacks`` which causes issues if
+    # we are iterating over it at the same time. We therefore
+    # make a copy to iterate over.
+    let currentCBs = callbacks
+    callbacks = @[]
+    # Using another sequence because callbacks themselves can add
+    # other callbacks.
+    var newCBs: seq[Callback] = @[]
+    for cb in currentCBs:
+      if newCBs.len > 0:
+        # A callback has already returned with EAGAIN, don't call any
+        # others until next `poll`.
+        newCBs.add(cb)
+      else:
+        if not cb(data.fd):
+          # Callback wants to be called again.
+          newCBs.add(cb)
+    callbacks = newCBs & callbacks
+
   proc poll*(timeout = 500) =
     let p = getGlobalDispatcher()
 
@@ -1055,24 +1075,11 @@ else:
         # so that exceptions can be raised from `send(...)` and
         # `recv(...)` routines.
 
-        if EvRead in info.events:
-          # Callback may add items to ``data.readCBs`` which causes issues if
-          # we are iterating over ``data.readCBs`` at the same time. We therefore
-          # make a copy to iterate over.
-          let currentCBs = data.readCBs
-          data.readCBs = @[]
-          for cb in currentCBs:
-            if not cb(data.fd):
-              # Callback wants to be called again.
-              data.readCBs.add(cb)
-
-        if EvWrite in info.events:
-          let currentCBs = data.writeCBs
-          data.writeCBs = @[]
-          for cb in currentCBs:
-            if not cb(data.fd):
-              # Callback wants to be called again.
-              data.writeCBs.add(cb)
+        if EvRead in info.events or info.events == {EvError}:
+          processCallbacks(data.readCBs)
+
+        if EvWrite in info.events or info.events == {EvError}:
+          processCallbacks(data.writeCBs)
 
         if info.key in p.selector:
           var newEvents: set[Event]
@@ -1410,7 +1417,7 @@ proc recvLine*(socket: AsyncFD): Future[string] {.async, deprecated.} =
 proc callSoon*(cbproc: proc ()) =
   ## Schedule `cbproc` to be called as soon as possible.
   ## The callback is called when control returns to the event loop.
-  getGlobalDispatcher().callbacks.enqueue(cbproc)
+  getGlobalDispatcher().callbacks.addLast(cbproc)
 
 proc runForever*() =
   ## Begins a never ending global dispatcher poll loop.
diff --git a/lib/pure/asyncfile.nim b/lib/pure/asyncfile.nim
index ffe6a391e..0241e4796 100644
--- a/lib/pure/asyncfile.nim
+++ b/lib/pure/asyncfile.nim
@@ -118,8 +118,8 @@ proc readBuffer*(f: AsyncFile, buf: pointer, size: int): Future[int] =
   ## Read ``size`` bytes from the specified file asynchronously starting at
   ## the current position of the file pointer.
   ##
-  ## If the file pointer is past the end of the file then an empty string is
-  ## returned.
+  ## If the file pointer is past the end of the file then zero is returned
+  ## and no bytes are read into ``buf``
   var retFuture = newFuture[int]("asyncfile.readBuffer")
 
   when defined(windows) or defined(nimdoc):
@@ -149,7 +149,11 @@ proc readBuffer*(f: AsyncFile, buf: pointer, size: int): Future[int] =
       let err = osLastError()
       if err.int32 != ERROR_IO_PENDING:
         GC_unref(ol)
-        retFuture.fail(newException(OSError, osErrorMsg(err)))
+        if err.int32 == ERROR_HANDLE_EOF:
+          # This happens in Windows Server 2003
+          retFuture.complete(0)
+        else:
+          retFuture.fail(newException(OSError, osErrorMsg(err)))
     else:
       # Request completed immediately.
       var bytesRead: DWord
@@ -233,7 +237,12 @@ proc read*(f: AsyncFile, size: int): Future[string] =
           dealloc buffer
           buffer = nil
         GC_unref(ol)
-        retFuture.fail(newException(OSError, osErrorMsg(err)))
+
+        if err.int32 == ERROR_HANDLE_EOF:
+          # This happens in Windows Server 2003
+          retFuture.complete("")
+        else:
+          retFuture.fail(newException(OSError, osErrorMsg(err)))
     else:
       # Request completed immediately.
       var bytesRead: DWord
diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim
index a658097f9..68da5f7c9 100644
--- a/lib/pure/asynchttpserver.nim
+++ b/lib/pure/asynchttpserver.nim
@@ -81,6 +81,17 @@ proc respond*(req: Request, code: HttpCode, content: string,
   ## content.
   ##
   ## This procedure will **not** close the client socket.
+  ##
+  ## Examples
+  ## --------
+  ## .. code-block::nim
+  ##    proc handler(req: Request) {.async.} =
+  ##      if req.url.path == "/hello-world":
+  ##        let msg = %* {"message": "Hello World"}
+  ##        let headers = newHttpHeaders([("Content-Type","application/json")])
+  ##        await req.respond(Http200, $msg, headers)
+  ##      else:
+  ##        await req.respond(Http404, "Not Found")
   var msg = "HTTP/1.1 " & $code & "\c\L"
 
   if headers != nil:
@@ -122,14 +133,21 @@ proc processClient(client: AsyncSocket, address: string,
     assert client != nil
     request.client = client
 
-    # First line - GET /path HTTP/1.1
-    lineFut.mget().setLen(0)
-    lineFut.clean()
-    await client.recvLineInto(lineFut) # TODO: Timeouts.
-    if lineFut.mget == "":
-      client.close()
-      return
+    # We should skip at least one empty line before the request
+    # https://tools.ietf.org/html/rfc7230#section-3.5
+    for i in 0..1:
+      lineFut.mget().setLen(0)
+      lineFut.clean()
+      await client.recvLineInto(lineFut) # TODO: Timeouts.
 
+      if lineFut.mget == "":
+        client.close()
+        return
+
+      if lineFut.mget != "\c\L":
+        break
+
+    # First line - GET /path HTTP/1.1
     var i = 0
     for linePart in lineFut.mget.split(' '):
       case i
diff --git a/lib/pure/collections/deques.nim b/lib/pure/collections/deques.nim
new file mode 100644
index 000000000..c25429778
--- /dev/null
+++ b/lib/pure/collections/deques.nim
@@ -0,0 +1,266 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2012 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Implementation of a `deque`:idx: (double-ended queue).
+## The underlying implementation uses a ``seq``.
+##
+## None of the procs that get an individual value from the deque can be used
+## on an empty deque.
+## If compiled with `boundChecks` option, those procs will raise an `IndexError`
+## on such access. This should not be relied upon, as `-d:release` will
+## disable those checks and may return garbage or crash the program.
+##
+## As such, a check to see if the deque is empty is needed before any
+## access, unless your program logic guarantees it indirectly.
+##
+## .. code-block:: Nim
+##   proc foo(a, b: Positive) =  # assume random positive values for `a` and `b`
+##     var deq = initDeque[int]()  # initializes the object
+##     for i in 1 ..< a: deq.addLast i  # populates the deque
+##
+##     if b < deq.len:  # checking before indexed access
+##       echo "The element at index position ", b, " is ", deq[b]
+##
+##     # The following two lines don't need any checking on access due to the
+##     # logic of the program, but that would not be the case if `a` could be 0.
+##     assert deq.peekFirst == 1
+##     assert deq.peekLast == a
+##
+##     while deq.len > 0:  # checking if the deque is empty
+##       echo deq.removeLast()
+##
+## Note: For inter thread communication use
+## a `Channel <channels.html>`_ instead.
+
+import math
+
+type
+  Deque*[T] = object
+    ## A double-ended queue backed with a ringed seq buffer.
+    data: seq[T]
+    head, tail, count, mask: int
+
+proc initDeque*[T](initialSize: int = 4): Deque[T] =
+  ## Create a new deque.
+  ## Optionally, the initial capacity can be reserved via `initialSize` as a
+  ## performance optimization. The length of a newly created deque will still
+  ## be 0.
+  ##
+  ## `initialSize` needs to be a power of two. If you need to accept runtime
+  ## values for this you could use the ``nextPowerOfTwo`` proc from the
+  ## `math <math.html>`_ module.
+  assert isPowerOfTwo(initialSize)
+  result.mask = initialSize-1
+  newSeq(result.data, initialSize)
+
+proc len*[T](deq: Deque[T]): int {.inline.} =
+  ## Return the number of elements of `deq`.
+  result = deq.count
+
+template emptyCheck(deq) =
+  # Bounds check for the regular deque access.
+  when compileOption("boundChecks"):
+    if unlikely(deq.count < 1):
+      raise newException(IndexError, "Empty deque.")
+
+template xBoundsCheck(deq, i) =
+  # Bounds check for the array like accesses.
+  when compileOption("boundChecks"):  # d:release should disable this.
+    if unlikely(i >= deq.count):  # x < deq.low is taken care by the Natural parameter
+      raise newException(IndexError,
+                         "Out of bounds: " & $i & " > " & $(deq.count - 1))
+
+proc `[]`*[T](deq: Deque[T], i: Natural) : T {.inline.} =
+  ## Access the i-th element of `deq` by order from first to last.
+  ## deq[0] is the first, deq[^1] is the last.
+  xBoundsCheck(deq, i)
+  return deq.data[(deq.first + i) and deq.mask]
+
+proc `[]`*[T](deq: var Deque[T], i: Natural): var T {.inline.} =
+  ## Access the i-th element of `deq` and returns a mutable
+  ## reference to it.
+  xBoundsCheck(deq, i)
+  return deq.data[(deq.head + i) and deq.mask]
+
+proc `[]=`* [T] (deq: var Deque[T], i: Natural, val : T) {.inline.} =
+  ## Change the i-th element of `deq`.
+  xBoundsCheck(deq, i)
+  deq.data[(deq.head + i) and deq.mask] = val
+
+iterator items*[T](deq: Deque[T]): T =
+  ## Yield every element of `deq`.
+  var i = deq.head
+  for c in 0 ..< deq.count:
+    yield deq.data[i]
+    i = (i + 1) and deq.mask
+
+iterator mitems*[T](deq: var Deque[T]): var T =
+  ## Yield every element of `deq`.
+  var i = deq.head
+  for c in 0 ..< deq.count:
+    yield deq.data[i]
+    i = (i + 1) and deq.mask
+
+iterator pairs*[T](deq: Deque[T]): tuple[key: int, val: T] =
+  ## Yield every (position, value) of `deq`.
+  var i = deq.head
+  for c in 0 ..< deq.count:
+    yield (c, deq.data[i])
+    i = (i + 1) and deq.mask
+
+proc contains*[T](deq: Deque[T], item: T): bool {.inline.} =
+  ## Return true if `item` is in `deq` or false if not found. Usually used
+  ## via the ``in`` operator. It is the equivalent of ``deq.find(item) >= 0``.
+  ##
+  ## .. code-block:: Nim
+  ##   if x in q:
+  ##     assert q.contains x
+  for e in deq:
+    if e == item: return true
+  return false
+
+proc expandIfNeeded[T](deq: var Deque[T]) =
+  var cap = deq.mask + 1
+  if unlikely(deq.count >= cap):
+    var n = newSeq[T](cap * 2)
+    for i, x in deq:  # don't use copyMem because the GC and because it's slower.
+      shallowCopy(n[i], x)
+    shallowCopy(deq.data, n)
+    deq.mask = cap * 2 - 1
+    deq.tail = deq.count
+    deq.head = 0
+
+proc addFirst*[T](deq: var Deque[T], item: T) =
+  ## Add an `item` to the beginning of the `deq`.
+  expandIfNeeded(deq)
+  inc deq.count
+  deq.head = (deq.head - 1) and deq.mask
+  deq.data[deq.head] = item
+
+proc addLast*[T](deq: var Deque[T], item: T) =
+  ## Add an `item` to the end of the `deq`.
+  expandIfNeeded(deq)
+  inc deq.count
+  deq.data[deq.tail] = item
+  deq.tail = (deq.tail + 1) and deq.mask
+
+proc peekFirst*[T](deq: Deque[T]): T {.inline.}=
+  ## Returns the first element of `deq`, but does not remove it from the deque.
+  emptyCheck(deq)
+  result = deq.data[deq.head]
+
+proc peekLast*[T](deq: Deque[T]): T {.inline.} =
+  ## Returns the last element of `deq`, but does not remove it from the deque.
+  emptyCheck(deq)
+  result = deq.data[(deq.tail - 1) and deq.mask]
+
+proc default[T](t: typedesc[T]): T {.inline.} = discard
+proc popFirst*[T](deq: var Deque[T]): T {.inline, discardable.} =
+  ## Remove and returns the first element of the `deq`.
+  emptyCheck(deq)
+  dec deq.count
+  result = deq.data[deq.head]
+  deq.data[deq.head] = default(type(result))
+  deq.head = (deq.head + 1) and deq.mask
+
+proc popLast*[T](deq: var Deque[T]): T {.inline, discardable.} =
+  ## Remove and returns the last element of the `deq`.
+  emptyCheck(deq)
+  dec deq.count
+  deq.tail = (deq.tail - 1) and deq.mask
+  result = deq.data[deq.tail]
+  deq.data[deq.tail] = default(type(result))
+
+proc `$`*[T](deq: Deque[T]): string =
+  ## Turn a deque into its string representation.
+  result = "["
+  for x in deq:
+    if result.len > 1: result.add(", ")
+    result.add($x)
+  result.add("]")
+
+when isMainModule:
+  var deq = initDeque[int](1)
+  deq.addLast(4)
+  deq.addFirst(9)
+  deq.addFirst(123)
+  var first = deq.popFirst()
+  deq.addLast(56)
+  assert(deq.peekLast() == 56)
+  deq.addLast(6)
+  assert(deq.peekLast() == 6)
+  var second = deq.popFirst()
+  deq.addLast(789)
+  assert(deq.peekLast() == 789)
+
+  assert first == 123
+  assert second == 9
+  assert($deq == "[4, 56, 6, 789]")
+
+  assert deq[0] == deq.peekFirst and deq.peekFirst == 4
+  assert deq[^1] == deq.peekLast and deq.peekLast == 789
+  deq[0] = 42
+  deq[^1] = 7
+
+  assert 6 in deq and 789 notin deq
+  assert deq.find(6) >= 0
+  assert deq.find(789) < 0
+
+  for i in -2 .. 10:
+    if i in deq:
+      assert deq.contains(i) and deq.find(i) >= 0
+    else:
+      assert(not deq.contains(i) and deq.find(i) < 0)
+
+  when compileOption("boundChecks"):
+    try:
+      echo deq[99]
+      assert false
+    except IndexError:
+      discard
+
+    try:
+      assert deq.len == 4
+      for i in 0 ..< 5: deq.popFirst()
+      assert false
+    except IndexError:
+      discard
+
+  # grabs some types of resize error.
+  deq = initDeque[int]()
+  for i in 1 .. 4: deq.addLast i
+  deq.popFirst()
+  deq.popLast()
+  for i in 5 .. 8: deq.addFirst i
+  assert $deq == "[8, 7, 6, 5, 2, 3]"
+
+  # Similar to proc from the documentation example
+  proc foo(a, b: Positive) = # assume random positive values for `a` and `b`.
+    var deq = initDeque[int]()
+    assert deq.len == 0
+    for i in 1 .. a: deq.addLast i
+
+    if b < deq.len: # checking before indexed access.
+      assert deq[b] == b + 1
+
+    # The following two lines don't need any checking on access due to the logic
+    # of the program, but that would not be the case if `a` could be 0.
+    assert deq.peekFirst == 1
+    assert deq.peekLast == a
+
+    while deq.len > 0: # checking if the deque is empty
+      assert deq.popFirst() > 0
+
+  #foo(0,0)
+  foo(8,5)
+  foo(10,9)
+  foo(1,1)
+  foo(2,1)
+  foo(1,5)
+  foo(3,2)
\ No newline at end of file
diff --git a/lib/pure/collections/queues.nim b/lib/pure/collections/queues.nim
index 399e4d413..e4d7eeef1 100644
--- a/lib/pure/collections/queues.nim
+++ b/lib/pure/collections/queues.nim
@@ -39,8 +39,10 @@
 
 import math
 
+{.warning: "`queues` module is deprecated - use `deques` instead".}
+
 type
-  Queue*[T] = object ## A queue.
+  Queue* {.deprecated.} [T] = object ## A queue.
     data: seq[T]
     rd, wr, count, mask: int
 
diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim
index f458b7636..45a148fbf 100644
--- a/lib/pure/collections/sequtils.nim
+++ b/lib/pure/collections/sequtils.nim
@@ -228,7 +228,7 @@ proc apply*[T](data: var seq[T], op: proc (x: var T) {.closure.})
   ##   var a = @["1", "2", "3", "4"]
   ##   echo repr(a)
   ##   # --> ["1", "2", "3", "4"]
-  ##   map(a, proc(x: var string) = x &= "42")
+  ##   apply(a, proc(x: var string) = x &= "42")
   ##   echo repr(a)
   ##   # --> ["142", "242", "342", "442"]
   ##
@@ -247,7 +247,7 @@ proc apply*[T](data: var seq[T], op: proc (x: T): T {.closure.})
   ##   var a = @["1", "2", "3", "4"]
   ##   echo repr(a)
   ##   # --> ["1", "2", "3", "4"]
-  ##   map(a, proc(x: string): string = x & "42")
+  ##   apply(a, proc(x: string): string = x & "42")
   ##   echo repr(a)
   ##   # --> ["142", "242", "342", "442"]
   ##
diff --git a/lib/pure/collections/tableimpl.nim b/lib/pure/collections/tableimpl.nim
index a3dfd43a1..674fdddd2 100644
--- a/lib/pure/collections/tableimpl.nim
+++ b/lib/pure/collections/tableimpl.nim
@@ -39,16 +39,22 @@ template rawGetKnownHCImpl() {.dirty.} =
     h = nextTry(h, maxHash(t))
   result = -1 - h                   # < 0 => MISSING; insert idx = -1 - result
 
-template rawGetImpl() {.dirty.} =
+template genHashImpl(key, hc: typed) =
   hc = hash(key)
   if hc == 0:       # This almost never taken branch should be very predictable.
     hc = 314159265  # Value doesn't matter; Any non-zero favorite is fine.
+
+template genHash(key: typed): Hash =
+  var res: Hash
+  genHashImpl(key, res)
+  res
+
+template rawGetImpl() {.dirty.} =
+  genHashImpl(key, hc)
   rawGetKnownHCImpl()
 
 template rawGetDeepImpl() {.dirty.} =   # Search algo for unconditional add
-  hc = hash(key)
-  if hc == 0:
-    hc = 314159265
+  genHashImpl(key, hc)
   var h: Hash = hc and maxHash(t)
   while isFilled(t.data[h].hcode):
     h = nextTry(h, maxHash(t))
diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim
index 778ea5ca3..e6e72d9ed 100644
--- a/lib/pure/collections/tables.nim
+++ b/lib/pure/collections/tables.nim
@@ -224,7 +224,7 @@ template withValue*[A, B](t: var Table[A, B], key: A,
 
 iterator allValues*[A, B](t: Table[A, B]; key: A): B =
   ## iterates over any value in the table `t` that belongs to the given `key`.
-  var h: Hash = hash(key) and high(t.data)
+  var h: Hash = genHash(key) and high(t.data)
   while isFilled(t.data[h].hcode):
     if t.data[h].key == key:
       yield t.data[h].val
@@ -338,7 +338,7 @@ proc hasKey*[A, B](t: TableRef[A, B], key: A): bool =
   ## returns true iff `key` is in the table `t`.
   result = t[].hasKey(key)
 
-template equalsImpl(t) =
+template equalsImpl(s, t: typed): typed =
   if s.counter == t.counter:
     # different insertion orders mean different 'data' seqs, so we have
     # to use the slow route here:
@@ -348,7 +348,9 @@ template equalsImpl(t) =
     return true
 
 proc `==`*[A, B](s, t: Table[A, B]): bool =
-  equalsImpl(t)
+  ## The `==` operator for hash tables. Returns ``true`` iff the content of both
+  ## tables contains the same key-value pairs. Insert order does not matter.
+  equalsImpl(s, t)
 
 proc indexBy*[A, B, C](collection: A, index: proc(x: B): C): Table[C, B] =
   ## Index the collection with the proc provided.
@@ -436,9 +438,12 @@ proc `$`*[A, B](t: TableRef[A, B]): string =
   dollarImpl()
 
 proc `==`*[A, B](s, t: TableRef[A, B]): bool =
+  ## The `==` operator for hash tables. Returns ``true`` iff either both tables
+  ## are ``nil`` or none is ``nil`` and the content of both tables contains the
+  ## same key-value pairs. Insert order does not matter.
   if isNil(s): result = isNil(t)
   elif isNil(t): result = false
-  else: equalsImpl(t[])
+  else: equalsImpl(s[], t[])
 
 proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): TableRef[C, B] =
   ## Index the collection with the proc provided.
@@ -464,13 +469,17 @@ proc len*[A, B](t: OrderedTable[A, B]): int {.inline.} =
   ## returns the number of keys in `t`.
   result = t.counter
 
-proc clear*[A, B](t: var OrderedTable[A, B] | OrderedTableRef[A, B]) =
+proc clear*[A, B](t: var OrderedTable[A, B]) =
   ## Resets the table so that it is empty.
   clearImpl()
   t.first = -1
   t.last = -1
 
-template forAllOrderedPairs(yieldStmt: untyped) {.oldimmediate, dirty.} =
+proc clear*[A, B](t: var OrderedTableRef[A, B]) =
+  ## Resets the table so that is is empty.
+  clear(t[])
+
+template forAllOrderedPairs(yieldStmt: untyped): typed {.dirty.} =
   var h = t.first
   while h >= 0:
     var nxt = t.data[h].next
@@ -606,6 +615,15 @@ proc `$`*[A, B](t: OrderedTable[A, B]): string =
   ## The `$` operator for ordered hash tables.
   dollarImpl()
 
+proc `==`*[A, B](s, t: OrderedTable[A, B]): bool =
+  ## The `==` operator for ordered hash tables. Returns true iff both the
+  ## content and the order are equal.
+  if s.counter == t.counter:
+    forAllOrderedPairs:
+      if s.data[h] != t.data[h]: return false
+    result = true
+  else: result = false
+
 proc sort*[A, B](t: var OrderedTable[A, B],
                  cmp: proc (x,y: (A, B)): int) =
   ## sorts `t` according to `cmp`. This modifies the internal list
@@ -656,13 +674,6 @@ proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} =
   ## returns the number of keys in `t`.
   result = t.counter
 
-template forAllOrderedPairs(yieldStmt: untyped) {.oldimmediate, dirty.} =
-  var h = t.first
-  while h >= 0:
-    var nxt = t.data[h].next
-    if isFilled(t.data[h].hcode): yieldStmt
-    h = nxt
-
 iterator pairs*[A, B](t: OrderedTableRef[A, B]): (A, B) =
   ## iterates over any (key, value) pair in the table `t` in insertion
   ## order.
@@ -738,7 +749,7 @@ proc newOrderedTable*[A, B](initialSize=64): OrderedTableRef[A, B] =
   ## values for this you could use the ``nextPowerOfTwo`` proc from the
   ## `math <math.html>`_ module or the ``rightSize`` proc from this module.
   new(result)
-  result[] = initOrderedTable[A, B]()
+  result[] = initOrderedTable[A, B](initialSize)
 
 proc newOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTableRef[A, B] =
   ## creates a new ordered hash table that contains the given `pairs`.
@@ -749,6 +760,14 @@ proc `$`*[A, B](t: OrderedTableRef[A, B]): string =
   ## The `$` operator for ordered hash tables.
   dollarImpl()
 
+proc `==`*[A, B](s, t: OrderedTableRef[A, B]): bool =
+  ## The `==` operator for ordered hash tables. Returns true iff either both
+  ## tables are ``nil`` or none is ``nil`` and the content and the order of
+  ## both are equal.
+  if isNil(s): result = isNil(t)
+  elif isNil(t): result = false
+  else: result = s[] == t[]
+
 proc sort*[A, B](t: OrderedTableRef[A, B],
                  cmp: proc (x,y: (A, B)): int) =
   ## sorts `t` according to `cmp`. This modifies the internal list
@@ -759,20 +778,22 @@ proc sort*[A, B](t: OrderedTableRef[A, B],
 
 proc del*[A, B](t: var OrderedTable[A, B], key: A) =
   ## deletes `key` from ordered hash table `t`. O(n) comlexity.
-  var prev = -1
-  let hc = hash(key)
-  forAllOrderedPairs:
-    if t.data[h].hcode == hc:
-      if t.first == h:
-        t.first = t.data[h].next
+  var n: OrderedKeyValuePairSeq[A, B]
+  newSeq(n, len(t.data))
+  var h = t.first
+  t.first = -1
+  t.last = -1
+  swap(t.data, n)
+  let hc = genHash(key)
+  while h >= 0:
+    var nxt = n[h].next
+    if isFilled(n[h].hcode):
+      if n[h].hcode == hc and n[h].key == key:
+        dec t.counter
       else:
-        t.data[prev].next = t.data[h].next
-      var zeroValue : type(t.data[h])
-      t.data[h] = zeroValue
-      dec t.counter
-      break
-    else:
-      prev = h
+        var j = -1 - rawGetKnownHC(t, n[h].key, n[h].hcode)
+        rawInsert(t, t.data, n[h].key, n[h].val, n[h].hcode, j)
+    h = nxt
 
 proc del*[A, B](t: var OrderedTableRef[A, B], key: A) =
   ## deletes `key` from ordered hash table `t`. O(n) comlexity.
@@ -916,11 +937,17 @@ proc `$`*[A](t: CountTable[A]): string =
   ## The `$` operator for count tables.
   dollarImpl()
 
+proc `==`*[A](s, t: CountTable[A]): bool =
+  ## The `==` operator for count tables. Returns ``true`` iff both tables
+  ## contain the same keys with the same count. Insert order does not matter.
+  equalsImpl(s, t)
+
 proc inc*[A](t: var CountTable[A], key: A, val = 1) =
   ## increments `t[key]` by `val`.
   var index = rawGet(t, key)
   if index >= 0:
     inc(t.data[index].val, val)
+    if t.data[index].val == 0: dec(t.counter)
   else:
     if mustRehash(len(t.data), t.counter): enlarge(t)
     rawInsert(t, t.data, key, val)
@@ -1040,6 +1067,14 @@ proc `$`*[A](t: CountTableRef[A]): string =
   ## The `$` operator for count tables.
   dollarImpl()
 
+proc `==`*[A](s, t: CountTableRef[A]): bool =
+  ## The `==` operator for count tables. Returns ``true`` iff either both tables
+  ## are ``nil`` or none is ``nil`` and both contain the same keys with the same
+  ## count. Insert order does not matter.
+  if isNil(s): result = isNil(t)
+  elif isNil(t): result = false
+  else: result = s[] == t[]
+
 proc inc*[A](t: CountTableRef[A], key: A, val = 1) =
   ## increments `t[key]` by `val`.
   t[].inc(key, val)
@@ -1124,6 +1159,20 @@ when isMainModule:
       doAssert(prev < i)
       prev = i
 
+  block: # Deletion from OrderedTable should account for collision groups. See issue #5057.
+    # The bug is reproducible only with exact keys
+    const key1 = "boy_jackpot.inGamma"
+    const key2 = "boy_jackpot.outBlack"
+
+    var t = {
+        key1: 0,
+        key2: 0
+    }.toOrderedTable()
+
+    t.del(key1)
+    assert(t.len == 1)
+    assert(key2 in t)
+
   var
     t1 = initCountTable[string]()
     t2 = initCountTable[string]()
diff --git a/lib/pure/future.nim b/lib/pure/future.nim
index 67975cfcb..2a6d29933 100644
--- a/lib/pure/future.nim
+++ b/lib/pure/future.nim
@@ -177,3 +177,24 @@ macro `[]`*(lc: ListComprehension, comp, typ: untyped): untyped =
               newIdentNode("@"),
               newNimNode(nnkBracket))),
           result))))
+
+
+macro dump*(x: typed): untyped =
+  ## Dumps the content of an expression, useful for debugging.
+  ## It accepts any expression and prints a textual representation
+  ## of the tree representing the expression - as it would appear in
+  ## source code - together with the value of the expression.
+  ##
+  ## As an example,
+  ##
+  ## .. code-block:: nim
+  ##   let
+  ##     x = 10
+  ##     y = 20
+  ##   dump(x + y)
+  ##
+  ## will print ``x + y = 30``.
+  let s = x.toStrLit
+  let r = quote do:
+    debugEcho `s`, " = ", `x`
+  return r
\ No newline at end of file
diff --git a/lib/pure/htmlparser.nim b/lib/pure/htmlparser.nim
index 10a786555..00c622d74 100644
--- a/lib/pure/htmlparser.nim
+++ b/lib/pure/htmlparser.nim
@@ -494,12 +494,15 @@ proc untilElementEnd(x: var XmlParser, result: XmlNode,
       else: discard
       result.addNode(parse(x, errors))
     of xmlElementEnd:
-      if cmpIgnoreCase(x.elemName, result.tag) == 0:
-        next(x)
-      else:
+      if cmpIgnoreCase(x.elemName, result.tag) != 0:
         #echo "5; expected: ", result.htmltag, " ", x.elemName
         adderr(expected(x, result))
-        # do not skip it here!
+        # this seems to do better match error corrections in browsers:
+        while x.kind in {xmlElementEnd, xmlWhitespace}:
+          if x.kind == xmlElementEnd and cmpIgnoreCase(x.elemName, result.tag) == 0:
+            break
+          next(x)
+      next(x)
       break
     of xmlEof:
       adderr(expected(x, result))
diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim
index 4404a9426..ba967b14f 100644
--- a/lib/pure/httpclient.nim
+++ b/lib/pure/httpclient.nim
@@ -18,14 +18,14 @@
 ##
 ## .. code-block:: Nim
 ##   var client = newHttpClient()
-##   echo(getContent("http://google.com"))
+##   echo client.getContent("http://google.com")
 ##
 ## The same action can also be performed asynchronously, simply use the
 ## ``AsyncHttpClient``:
 ##
 ## .. code-block:: Nim
 ##   var client = newAsyncHttpClient()
-##   echo(await getContent("http://google.com"))
+##   echo await client.getContent("http://google.com")
 ##
 ## The functionality implemented by ``HttpClient`` and ``AsyncHttpClient``
 ## is the same, so you can use whichever one suits you best in the examples
@@ -50,6 +50,20 @@
 ##
 ##   echo client.postContent("http://validator.w3.org/check", multipart=data)
 ##
+## You can also make post requests with custom headers. 
+## This example sets ``Content-Type`` to ``application/json``
+## and uses a json object for the body
+##
+## .. code-block:: Nim
+##   import httpclient, json
+##   
+##   let client = newHttpClient()
+##   client.headers = newHttpHeaders({ "Content-Type": "application/json" })
+##   let body = %*{
+##       "data": "some text"
+##   }
+##   echo client.request("http://some.api", httpMethod = HttpPost, body = $body)
+##
 ## Progress reporting
 ## ==================
 ##
@@ -103,7 +117,7 @@
 ## only basic authentication is supported at the moment.
 
 import net, strutils, uri, parseutils, strtabs, base64, os, mimetypes,
-  math, random, httpcore, times
+  math, random, httpcore, times, tables
 import asyncnet, asyncdispatch
 import nativesockets
 
@@ -432,7 +446,7 @@ proc request*(url: string, httpMethod: string, extraHeaders = "",
 
 
   # get the socket ready. If we are connecting through a proxy to SSL,
-  # send the appropiate CONNECT header. If not, simply connect to the proper
+  # send the appropriate CONNECT header. If not, simply connect to the proper
   # host (which may still be the proxy, for normal HTTP)
   if proxy != nil and hostUrl.scheme == "https":
     when defined(ssl):
@@ -736,7 +750,7 @@ proc newHttpClient*(userAgent = defUserAgent,
   ## ``proxy`` specifies an HTTP proxy to use for this HTTP client's
   ## connections.
   ##
-  ## ``timeout`` specifies the number of miliseconds to allow before a
+  ## ``timeout`` specifies the number of milliseconds to allow before a
   ## ``TimeoutError`` is raised.
   new result
   result.headers = newHttpHeaders()
@@ -944,8 +958,10 @@ proc parseResponse(client: HttpClient | AsyncHttpClient,
 proc newConnection(client: HttpClient | AsyncHttpClient,
                    url: Uri) {.multisync.} =
   if client.currentURL.hostname != url.hostname or
-      client.currentURL.scheme != url.scheme:
-    if client.connected: client.close()
+      client.currentURL.scheme != url.scheme or
+      client.currentURL.port != url.port:
+    if client.connected:
+      client.close()
 
     when client is HttpClient:
       client.socket = newSocket()
@@ -973,8 +989,20 @@ proc newConnection(client: HttpClient | AsyncHttpClient,
     client.currentURL = url
     client.connected = true
 
+proc override(fallback, override: HttpHeaders): HttpHeaders =
+  # Right-biased map union for `HttpHeaders`
+  if override.isNil:
+    return fallback
+
+  result = newHttpHeaders()
+  # Copy by value
+  result.table[] = fallback.table[]
+  for k, vs in override.table:
+    result[k] = vs
+
 proc request*(client: HttpClient | AsyncHttpClient, url: string,
-              httpMethod: string, body = ""): Future[Response] {.multisync.} =
+              httpMethod: string, body = "",
+              headers: HttpHeaders = nil): Future[Response] {.multisync.} =
   ## Connects to the hostname specified by the URL and performs a request
   ## using the custom method string specified by ``httpMethod``.
   ##
@@ -1007,13 +1035,15 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string,
   else:
     await newConnection(client, connectionUrl)
 
-  if not client.headers.hasKey("user-agent") and client.userAgent != "":
-    client.headers["User-Agent"] = client.userAgent
+  let effectiveHeaders = client.headers.override(headers)
+
+  if not effectiveHeaders.hasKey("user-agent") and client.userAgent != "":
+    effectiveHeaders["User-Agent"] = client.userAgent
 
-  var headers = generateHeaders(requestUrl, httpMethod,
-                                client.headers, body, client.proxy)
+  var headersString = generateHeaders(requestUrl, httpMethod,
+                                      effectiveHeaders, body, client.proxy)
 
-  await client.socket.send(headers)
+  await client.socket.send(headersString)
   if body != "":
     await client.socket.send(body)
 
@@ -1024,7 +1054,8 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string,
   client.proxy = savedProxy
 
 proc request*(client: HttpClient | AsyncHttpClient, url: string,
-              httpMethod = HttpGET, body = ""): Future[Response] {.multisync.} =
+              httpMethod = HttpGET, body = "",
+              headers: HttpHeaders = nil): Future[Response] {.multisync.} =
   ## Connects to the hostname specified by the URL and performs a request
   ## using the method specified.
   ##
@@ -1034,7 +1065,8 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string,
   ##
   ## When a request is made to a different hostname, the current connection will
   ## be closed.
-  result = await request(client, url, $httpMethod, body)
+  result = await request(client, url, $httpMethod, body,
+                         headers = headers)
 
 proc get*(client: HttpClient | AsyncHttpClient,
           url: string): Future[Response] {.multisync.} =
@@ -1081,18 +1113,22 @@ proc post*(client: HttpClient | AsyncHttpClient, url: string, body = "",
     else:
       x
   var xb = mpBody.withNewLine() & body
+
+  var headers = newHttpHeaders()
   if multipart != nil:
-    client.headers["Content-Type"] = mpHeader.split(": ")[1]
-  client.headers["Content-Length"] = $len(xb)
+    headers["Content-Type"] = mpHeader.split(": ")[1]
+  headers["Content-Length"] = $len(xb)
 
-  result = await client.request(url, HttpPOST, xb)
+  result = await client.request(url, HttpPOST, xb,
+                                headers = headers)
   # Handle redirects.
   var lastURL = url
   for i in 1..client.maxRedirects:
     if result.status.redirection():
       let redirectTo = getNewLocation(lastURL, result.headers)
       var meth = if result.status != "307": HttpGet else: HttpPost
-      result = await client.request(redirectTo, meth, xb)
+      result = await client.request(redirectTo, meth, xb,
+                                    headers = headers)
       lastURL = redirectTo
 
 proc postContent*(client: HttpClient | AsyncHttpClient, url: string,
diff --git a/lib/pure/includes/asyncfutures.nim b/lib/pure/includes/asyncfutures.nim
index d78464a91..029c5f157 100644
--- a/lib/pure/includes/asyncfutures.nim
+++ b/lib/pure/includes/asyncfutures.nim
@@ -246,6 +246,7 @@ proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
 proc all*[T](futs: varargs[Future[T]]): auto =
   ## Returns a future which will complete once
   ## all futures in ``futs`` complete.
+  ## If the argument is empty, the returned future completes immediately.
   ##
   ## If the awaited futures are not ``Future[void]``, the returned future
   ## will hold the values of all awaited futures in a sequence.
@@ -262,13 +263,16 @@ proc all*[T](futs: varargs[Future[T]]): auto =
 
     for fut in futs:
       fut.callback = proc(f: Future[T]) =
-        if f.failed:
-          retFuture.fail(f.error)
-        elif not retFuture.finished:
-          inc(completedFutures)
+        inc(completedFutures)
+        if not retFuture.finished:
+          if f.failed:
+            retFuture.fail(f.error)
+          else:
+            if completedFutures == totalFutures:
+              retFuture.complete()
 
-          if completedFutures == totalFutures:
-            retFuture.complete()
+    if totalFutures == 0:
+      retFuture.complete()
 
     return retFuture
 
@@ -281,15 +285,19 @@ proc all*[T](futs: varargs[Future[T]]): auto =
     for i, fut in futs:
       proc setCallback(i: int) =
         fut.callback = proc(f: Future[T]) =
-          if f.failed:
-            retFuture.fail(f.error)
-          elif not retFuture.finished:
-            retValues[i] = f.read()
-            inc(completedFutures)
+          inc(completedFutures)
+          if not retFuture.finished:
+            if f.failed:
+              retFuture.fail(f.error)
+            else:
+              retValues[i] = f.read()
 
-            if completedFutures == len(retValues):
-              retFuture.complete(retValues)
+              if completedFutures == len(retValues):
+                retFuture.complete(retValues)
 
       setCallback(i)
 
+    if retValues.len == 0:
+      retFuture.complete(retValues)
+
     return retFuture
diff --git a/lib/pure/ioselectors.nim b/lib/pure/ioselectors.nim
index adb3497ac..744bdbaa1 100644
--- a/lib/pure/ioselectors.nim
+++ b/lib/pure/ioselectors.nim
@@ -52,13 +52,13 @@ when defined(nimdoc):
       Vnode,       ## BSD specific file change happens
       User,        ## User event is raised
       Error,       ## Error happens while waiting, for descriptor
-      VnodeWrite,  ## NOTE_WRITE (BSD specific, write to file occured)
-      VnodeDelete, ## NOTE_DELETE (BSD specific, unlink of file occured)
+      VnodeWrite,  ## NOTE_WRITE (BSD specific, write to file occurred)
+      VnodeDelete, ## NOTE_DELETE (BSD specific, unlink of file occurred)
       VnodeExtend, ## NOTE_EXTEND (BSD specific, file extended)
       VnodeAttrib, ## NOTE_ATTRIB (BSD specific, file attributes changed)
       VnodeLink,   ## NOTE_LINK (BSD specific, file link count changed)
       VnodeRename, ## NOTE_RENAME (BSD specific, file renamed)
-      VnodeRevoke  ## NOTE_REVOKE (BSD specific, file revoke occured)
+      VnodeRevoke  ## NOTE_REVOKE (BSD specific, file revoke occurred)
 
     ReadyKey*[T] = object
       ## An object which holds result for descriptor
@@ -140,7 +140,7 @@ when defined(nimdoc):
 
   proc flush*[T](s: Selector[T]) =
     ## Flushes all changes was made to kernel pool/queue.
-    ## This function is usefull only for BSD and MacOS, because
+    ## This function is useful only for BSD and MacOS, because
     ## kqueue supports bulk changes to be made.
     ## On Linux/Windows and other Posix compatible operation systems,
     ## ``flush`` is alias for `discard`.
diff --git a/lib/pure/ioselects/ioselectors_kqueue.nim b/lib/pure/ioselects/ioselectors_kqueue.nim
index cdaeeae26..3c0cf4e90 100644
--- a/lib/pure/ioselects/ioselectors_kqueue.nim
+++ b/lib/pure/ioselects/ioselectors_kqueue.nim
@@ -26,8 +26,8 @@ when defined(macosx) or defined(freebsd):
     const MAX_DESCRIPTORS_ID = 29 # KERN_MAXFILESPERPROC (MacOS)
   else:
     const MAX_DESCRIPTORS_ID = 27 # KERN_MAXFILESPERPROC (FreeBSD)
-  proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr int,
-              newp: pointer, newplen: int): cint
+  proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize,
+              newp: pointer, newplen: csize): cint
        {.importc: "sysctl",header: """#include <sys/types.h>
                                       #include <sys/sysctl.h>"""}
 elif defined(netbsd) or defined(openbsd):
@@ -35,8 +35,8 @@ elif defined(netbsd) or defined(openbsd):
   # KERN_MAXFILES, because KERN_MAXFILES is always bigger,
   # than KERN_MAXFILESPERPROC.
   const MAX_DESCRIPTORS_ID = 7 # KERN_MAXFILES
-  proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr int,
-              newp: pointer, newplen: int): cint
+  proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize,
+              newp: pointer, newplen: csize): cint
        {.importc: "sysctl",header: """#include <sys/param.h>
                                       #include <sys/sysctl.h>"""}
 
@@ -72,7 +72,7 @@ type SelectEvent* = ptr SelectEventImpl
 
 proc newSelector*[T](): Selector[T] =
   var maxFD = 0.cint
-  var size = sizeof(cint)
+  var size = csize(sizeof(cint))
   var namearr = [1.cint, MAX_DESCRIPTORS_ID.cint]
   # Obtain maximum number of file descriptors for process
   if sysctl(addr(namearr[0]), 2, cast[pointer](addr maxFD), addr size,
diff --git a/lib/pure/json.nim b/lib/pure/json.nim
index 0b7908c02..5fff7352f 100644
--- a/lib/pure/json.nim
+++ b/lib/pure/json.nim
@@ -954,9 +954,11 @@ proc newIndent(curr, indent: int, ml: bool): int =
 proc nl(s: var string, ml: bool) =
   if ml: s.add("\n")
 
-proc escapeJson*(s: string): string =
+proc escapeJson*(s: string; result: var string) =
   ## Converts a string `s` to its JSON representation.
-  result = newStringOfCap(s.len + s.len shr 3)
+  ## Appends to ``result``.
+  const
+    HexChars = "0123456789ABCDEF"
   result.add("\"")
   for x in runes(s):
     var r = int(x)
@@ -967,10 +969,19 @@ proc escapeJson*(s: string): string =
       of '\\': result.add("\\\\")
       else: result.add(c)
     else:
-      result.add("\\u")
-      result.add(toHex(r, 4))
+      # toHex inlined for more speed (saves stupid string allocations):
+      result.add("\\u0000")
+      let start = result.len - 4
+      for j in countdown(3, 0):
+        result[j+start] = HexChars[r and 0xF]
+        r = r shr 4
   result.add("\"")
 
+proc escapeJson*(s: string): string =
+  ## Converts a string `s` to its JSON representation.
+  result = newStringOfCap(s.len + s.len shr 3)
+  escapeJson(s, result)
+
 proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true,
               lstArr = false, currIndent = 0) =
   case node.kind
@@ -988,7 +999,7 @@ proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true,
         inc i
         # Need to indent more than {
         result.indent(newIndent(currIndent, indent, ml))
-        result.add(escapeJson(key))
+        escapeJson(key, result)
         result.add(": ")
         toPretty(result, val, indent, ml, false,
                  newIndent(currIndent, indent, ml))
@@ -999,16 +1010,19 @@ proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true,
       result.add("{}")
   of JString:
     if lstArr: result.indent(currIndent)
-    result.add(escapeJson(node.str))
+    escapeJson(node.str, result)
   of JInt:
     if lstArr: result.indent(currIndent)
-    result.add($node.num)
+    when defined(js): result.add($node.num)
+    else: result.add(node.num)
   of JFloat:
     if lstArr: result.indent(currIndent)
-    result.add($node.fnum)
+    # Fixme: implement new system.add ops for the JS target
+    when defined(js): result.add($node.fnum)
+    else: result.add(node.fnum)
   of JBool:
     if lstArr: result.indent(currIndent)
-    result.add($node.bval)
+    result.add(if node.bval: "true" else: "false")
   of JArray:
     if lstArr: result.indent(currIndent)
     if len(node.elems) != 0:
@@ -1057,16 +1071,18 @@ proc toUgly*(result: var string, node: JsonNode) =
     for key, value in pairs(node.fields):
       if comma: result.add ","
       else:     comma = true
-      result.add key.escapeJson()
+      key.escapeJson(result)
       result.add ":"
       result.toUgly value
     result.add "}"
   of JString:
-    result.add node.str.escapeJson()
+    node.str.escapeJson(result)
   of JInt:
-    result.add($node.num)
+    when defined(js): result.add($node.num)
+    else: result.add(node.num)
   of JFloat:
-    result.add($node.fnum)
+    when defined(js): result.add($node.fnum)
+    else: result.add(node.fnum)
   of JBool:
     result.add(if node.bval: "true" else: "false")
   of JNull:
@@ -1394,4 +1410,6 @@ when isMainModule:
     var parsed2 = parseFile("tests/testdata/jsontest2.json")
     doAssert(parsed2{"repository", "description"}.str=="IRC Library for Haskell", "Couldn't fetch via multiply nested key using {}")
 
+  doAssert escapeJson("\10FoobarÄ") == "\"\\u000AFoobar\\u00C4\""
+
   echo("Tests succeeded!")
diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim
index b23b1e5bb..5544a4b3f 100644
--- a/lib/pure/logging.nim
+++ b/lib/pure/logging.nim
@@ -172,18 +172,26 @@ when not defined(js):
     var (path, name, _) = splitFile(getAppFilename())
     result = changeFileExt(path / name, "log")
 
+  proc newFileLogger*(file: File,
+                      levelThreshold = lvlAll,
+                      fmtStr = defaultFmtStr): FileLogger =
+    ## Creates a new file logger. This logger logs to ``file``.
+    new(result)
+    result.file = file
+    result.levelThreshold = levelThreshold
+    result.fmtStr = fmtStr
+
   proc newFileLogger*(filename = defaultFilename(),
                       mode: FileMode = fmAppend,
                       levelThreshold = lvlAll,
                       fmtStr = defaultFmtStr,
                       bufSize: int = -1): FileLogger =
-    ## Creates a new file logger. This logger logs to a file.
+    ## Creates a new file logger. This logger logs to a file, specified
+    ## by ``fileName``.
     ## Use ``bufSize`` as size of the output buffer when writing the file
     ## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size).
-    new(result)
-    result.levelThreshold = levelThreshold
-    result.file = open(filename, mode, bufSize = bufSize)
-    result.fmtStr = fmtStr
+    let file = open(filename, mode, bufSize = bufSize)
+    newFileLogger(file, levelThreshold, fmtStr)
 
   # ------
 
diff --git a/lib/pure/marshal.nim b/lib/pure/marshal.nim
index 36e6cf52f..c4c731acf 100644
--- a/lib/pure/marshal.nim
+++ b/lib/pure/marshal.nim
@@ -9,6 +9,7 @@
 
 ## This module contains procs for `serialization`:idx: and `deseralization`:idx:
 ## of arbitrary Nim data structures. The serialization format uses `JSON`:idx:.
+## Warning: The serialization format could change in future!
 ##
 ## **Restriction**: For objects their type is **not** serialized. This means
 ## essentially that it does not work if the object has some other runtime
@@ -29,6 +30,12 @@
 ##   a = b
 ##   echo($$a[]) # produces "{}", not "{f: 0}"
 ##
+##   # unmarshal
+##   let c = to[B]("""{"f": 2}""")
+##
+##   # marshal
+##   let s = $$c
+
 ## **Note**: The ``to`` and ``$$`` operations are available at compile-time!
 
 import streams, typeinfo, json, intsets, tables, unicode
diff --git a/lib/pure/net.nim b/lib/pure/net.nim
index d4f239c49..863a8a6f4 100644
--- a/lib/pure/net.nim
+++ b/lib/pure/net.nim
@@ -173,7 +173,7 @@ type
 
 
 proc socketError*(socket: Socket, err: int = -1, async = false,
-                  lastError = (-1).OSErrorCode): void
+                  lastError = (-1).OSErrorCode): void {.gcsafe.}
 
 proc isDisconnectionError*(flags: set[SocketFlag],
     lastError: OSErrorCode): bool =
@@ -1250,7 +1250,7 @@ proc IPv6_loopback*(): IpAddress =
     address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])
 
 proc `==`*(lhs, rhs: IpAddress): bool =
-  ## Compares two IpAddresses for Equality. Returns two if the addresses are equal
+  ## Compares two IpAddresses for Equality. Returns true if the addresses are equal
   if lhs.family != rhs.family: return false
   if lhs.family == IpAddressFamily.IPv4:
     for i in low(lhs.address_v4) .. high(lhs.address_v4):
diff --git a/lib/pure/nimtracker.nim b/lib/pure/nimtracker.nim
new file mode 100644
index 000000000..52fa9da77
--- /dev/null
+++ b/lib/pure/nimtracker.nim
@@ -0,0 +1,80 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2016 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Memory tracking support for Nim.
+
+when not defined(memTracker) and not isMainModule:
+  {.error: "Memory tracking support is turned off!".}
+
+{.push memtracker: off.}
+# we import the low level wrapper and are careful not to use Nim's
+# memory manager for anything here.
+import sqlite3
+
+var
+  dbHandle: PSqlite3
+  insertStmt: Pstmt
+
+template sbind(x: int; value) =
+  when value is cstring:
+    let ret = insertStmt.bindText(x, value, value.len.int32, SQLITE_TRANSIENT)
+    if ret != SQLITE_OK:
+      quit "could not bind value"
+  else:
+    let ret = insertStmt.bindInt64(x, value)
+    if ret != SQLITE_OK:
+      quit "could not bind value"
+
+when defined(memTracker):
+  proc logEntries(log: TrackLog) {.nimcall, locks: 0, tags: [].} =
+    for i in 0..log.count-1:
+      var success = false
+      let e = log.data[i]
+      discard sqlite3.reset(insertStmt)
+      discard clearBindings(insertStmt)
+      sbind 1, e.op
+      sbind(2, cast[int](e.address))
+      sbind 3, e.size
+      sbind 4, e.file
+      sbind 5, e.line
+      if step(insertStmt) == SQLITE_DONE:
+        success = true
+      if not success:
+        quit "could not write to database!"
+
+proc execQuery(q: string) =
+  var s: Pstmt
+  if prepare_v2(dbHandle, q, q.len.int32, s, nil) == SQLITE_OK:
+    discard step(s)
+    if finalize(s) != SQLITE_OK:
+      quit "could not finalize " & $sqlite3.errmsg(dbHandle)
+  else:
+    quit "could not prepare statement " & $sqlite3.errmsg(dbHandle)
+
+proc setupDb() =
+  execQuery """create table if not exists tracking(
+       id integer primary key,
+       op varchar not null,
+       address integer not null,
+       size integer not null,
+       file varchar not null,
+       line integer not null
+     )"""
+  execQuery "delete from tracking"
+
+if sqlite3.open("memtrack.db", dbHandle) == SQLITE_OK:
+  setupDb()
+  const query = "INSERT INTO tracking(op, address, size, file, line) values (?, ?, ?, ?, ?)"
+  if prepare_v2(dbHandle, query,
+      query.len, insertStmt, nil) == SQLITE_OK:
+    when defined(memTracker):
+      setTrackLogger logEntries
+  else:
+    quit "could not prepare statement B " & $sqlite3.errmsg(dbHandle)
+{.pop.}
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index cdbe170cc..f077e798a 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -50,6 +50,8 @@ proc c_getenv(env: cstring): cstring {.
   importc: "getenv", header: "<stdlib.h>".}
 proc c_putenv(env: cstring): cint {.
   importc: "putenv", header: "<stdlib.h>".}
+proc c_free(p: pointer) {.
+  importc: "free", header: "<stdlib.h>".}
 
 var errno {.importc, header: "<errno.h>".}: cint
 
@@ -303,24 +305,47 @@ proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} =
 
 proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} =
   ## Returns the `current working directory`:idx:.
-  const bufsize = 512 # should be enough
   when defined(windows):
+    var bufsize = MAX_PATH.int32
     when useWinUnicode:
       var res = newWideCString("", bufsize)
-      var L = getCurrentDirectoryW(bufsize, res)
-      if L == 0'i32: raiseOSError(osLastError())
-      result = res$L
+      while true:
+        var L = getCurrentDirectoryW(bufsize, res)
+        if L == 0'i32:
+          raiseOSError(osLastError())
+        elif L > bufsize:
+          res = newWideCString("", L)
+          bufsize = L
+        else:
+          result = res$L
+          break
     else:
       result = newString(bufsize)
-      var L = getCurrentDirectoryA(bufsize, result)
-      if L == 0'i32: raiseOSError(osLastError())
-      setLen(result, L)
+      while true:
+        var L = getCurrentDirectoryA(bufsize, result)
+        if L == 0'i32:
+          raiseOSError(osLastError())
+        elif L > bufsize:
+          result = newString(L)
+          bufsize = L
+        else:
+          setLen(result, L)
+          break
   else:
+    var bufsize = 1024 # should be enough
     result = newString(bufsize)
-    if getcwd(result, bufsize) != nil:
-      setLen(result, c_strlen(result))
-    else:
-      raiseOSError(osLastError())
+    while true:
+      if getcwd(result, bufsize) != nil:
+        setLen(result, c_strlen(result))
+        break
+      else:
+        let err = osLastError()
+        if err.int32 == ERANGE:
+          bufsize = bufsize shl 1
+          doAssert(bufsize >= 0)
+          result = newString(bufsize)
+        else:
+          raiseOSError(osLastError())
 
 proc setCurrentDir*(newDir: string) {.inline, tags: [].} =
   ## Sets the `current working directory`:idx:; `OSError` is raised if
@@ -336,28 +361,45 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [].} =
 
 proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
   tags: [ReadDirEffect].} =
-  ## Returns the full (`absolute`:idx:) path of the file `filename`, raises OSError in case of an error.
+  ## Returns the full (`absolute`:idx:) path of the file `filename`,
+  ## raises OSError in case of an error.
   when defined(windows):
-    const bufsize = 3072'i32
+    var bufsize = MAX_PATH.int32
     when useWinUnicode:
-      var unused: WideCString
-      var res = newWideCString("", bufsize div 2)
-      var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused)
-      if L <= 0'i32 or L >= bufsize:
-        raiseOSError(osLastError())
-      result = res$L
+      var unused: WideCString = nil
+      var res = newWideCString("", bufsize)
+      while true:
+        var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused)
+        if L == 0'i32:
+          raiseOSError(osLastError())
+        elif L > bufsize:
+          res = newWideCString("", L)
+          bufsize = L
+        else:
+          result = res$L
+          break
     else:
-      var unused: cstring
+      var unused: cstring = nil
       result = newString(bufsize)
-      var L = getFullPathNameA(filename, bufsize, result, unused)
-      if L <= 0'i32 or L >= bufsize: raiseOSError(osLastError())
-      setLen(result, L)
+      while true:
+        var L = getFullPathNameA(filename, bufsize, result, unused)
+        if L == 0'i32:
+          raiseOSError(osLastError())
+        elif L > bufsize:
+          result = newString(L)
+          bufsize = L
+        else:
+          setLen(result, L)
+          break
   else:
-    # careful, realpath needs to take an allocated buffer according to Posix:
-    result = newString(pathMax)
-    var r = realpath(filename, result)
-    if r.isNil: raiseOSError(osLastError())
-    setLen(result, c_strlen(result))
+    # according to Posix we don't need to allocate space for result pathname.
+    # But we need to free return value with free(3).
+    var r = realpath(filename, nil)
+    if r.isNil:
+      raiseOSError(osLastError())
+    else:
+      result = $r
+      c_free(cast[pointer](r))
 
 when defined(Windows):
   proc openHandle(path: string, followSymlink=true): Handle =
@@ -571,6 +613,7 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
       if bytesread != bufSize: break
     dealloc(buf)
     close(s)
+    flushFile(d)
     close(d)
 
 proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
@@ -1009,27 +1052,61 @@ proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [
     of pcDir: removeDir(path)
   rawRemoveDir(dir)
 
-proc rawCreateDir(dir: string) =
+proc rawCreateDir(dir: string): bool =
+  # Try to create one directory (not the whole path).
+  # returns `true` for success, `false` if the path has previously existed
+  #
+  # This is a thin wrapper over mkDir (or alternatives on other systems),
+  # so in case of a pre-existing path we don't check that it is a directory.
   when defined(solaris):
-    if mkdir(dir, 0o777) != 0'i32 and errno != EEXIST and errno != ENOSYS:
+    let res = mkdir(dir, 0o777)
+    if res == 0'i32:
+      result = true
+    elif errno in {EEXIST, ENOSYS}:
+      result = false
+    else:
       raiseOSError(osLastError())
   elif defined(unix):
-    if mkdir(dir, 0o777) != 0'i32 and errno != EEXIST:
+    let res = mkdir(dir, 0o777)
+    if res == 0'i32:
+      result = true
+    elif errno == EEXIST:
+      result = false
+    else:
+      echo res
       raiseOSError(osLastError())
   else:
     when useWinUnicode:
       wrapUnary(res, createDirectoryW, dir)
     else:
-      var res = createDirectoryA(dir)
-    if res == 0'i32 and getLastError() != 183'i32:
+      let res = createDirectoryA(dir)
+
+    if res != 0'i32:
+      result = true
+    elif getLastError() == 183'i32:
+      result = false
+    else:
       raiseOSError(osLastError())
 
-proc createDir*(dir: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
+proc existsOrCreateDir*(dir: string): bool =
+  ## Check if a `directory`:idx: `dir` exists, and create it otherwise.
+  ##
+  ## Does not create parent directories (fails if parent does not exist).
+  ## Returns `true` if the directory already exists, and `false`
+  ## otherwise.
+  result = not rawCreateDir(dir)
+  if result:
+    # path already exists - need to check that it is indeed a directory
+    if not existsDir(dir):
+      raise newException(IOError, "Failed to create the directory")
+
+proc createDir*(dir: string) {.rtl, extern: "nos$1",
+  tags: [WriteDirEffect, ReadDirEffect].} =
   ## Creates the `directory`:idx: `dir`.
   ##
   ## The directory may contain several subdirectories that do not exist yet.
   ## The full path is created. If this fails, `OSError` is raised. It does **not**
-  ## fail if the path already exists because for most usages this does not
+  ## fail if the directory already exists because for most usages this does not
   ## indicate an error.
   var omitNext = false
   when doslike:
@@ -1039,8 +1116,12 @@ proc createDir*(dir: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
       if omitNext:
         omitNext = false
       else:
-        rawCreateDir(substr(dir, 0, i-1))
-  rawCreateDir(dir)
+        discard existsOrCreateDir(substr(dir, 0, i-1))
+
+  # The loop does not create the dir itself if it doesn't end in separator
+  if dir.len > 0 and not omitNext and
+     dir[^1] notin {DirSep, AltSep}:
+    discard existsOrCreateDir(dir)
 
 proc copyDir*(source, dest: string) {.rtl, extern: "nos$1",
   tags: [WriteIOEffect, ReadIOEffect], benign.} =
@@ -1382,6 +1463,35 @@ when declared(paramCount) or defined(nimdoc):
     for i in 1..paramCount():
       result.add(paramStr(i))
 
+when defined(freebsd):
+  proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize,
+              newp: pointer, newplen: csize): cint
+       {.importc: "sysctl",header: """#include <sys/types.h>
+                                      #include <sys/sysctl.h>"""}
+  const
+    CTL_KERN = 1
+    KERN_PROC = 14
+    KERN_PROC_PATHNAME = 12
+    MAX_PATH = 1024
+
+  proc getApplFreebsd(): string =
+    var pathLength = csize(MAX_PATH)
+    result = newString(pathLength)
+    var req = [CTL_KERN.cint, KERN_PROC.cint, KERN_PROC_PATHNAME.cint, -1.cint]
+    while true:
+      let res = sysctl(addr req[0], 4, cast[pointer](addr result[0]),
+                       addr pathLength, nil, 0)
+      if res < 0:
+        let err = osLastError()
+        if err.int32 == ENOMEM:
+          result = newString(pathLength)
+        else:
+          result.setLen(0) # error!
+          break
+      else:
+        result.setLen(pathLength)
+        break
+
 when defined(linux) or defined(solaris) or defined(bsd) or defined(aix):
   proc getApplAux(procPath: string): string =
     result = newString(256)
@@ -1426,16 +1536,34 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} =
   # Solaris:
   # /proc/<pid>/object/a.out (filename only)
   # /proc/<pid>/path/a.out (complete pathname)
-  # FreeBSD: /proc/<pid>/file
   when defined(windows):
+    var bufsize = int32(MAX_PATH)
     when useWinUnicode:
-      var buf = newWideCString("", 256)
-      var len = getModuleFileNameW(0, buf, 256)
-      result = buf$len
+      var buf = newWideCString("", bufsize)
+      while true:
+        var L = getModuleFileNameW(0, buf, bufsize)
+        if L == 0'i32:
+          result = "" # error!
+          break
+        elif L > bufsize:
+          buf = newWideCString("", L)
+          bufsize = L
+        else:
+          result = buf$L
+          break
     else:
-      result = newString(256)
-      var len = getModuleFileNameA(0, result, 256)
-      setlen(result, int(len))
+      result = newString(bufsize)
+      while true:
+        var L = getModuleFileNameA(0, result, bufsize)
+        if L == 0'i32:
+          result = "" # error!
+          break
+        elif L > bufsize:
+          result = newString(L)
+          bufsize = L
+        else:
+          setLen(result, L)
+          break
   elif defined(macosx):
     var size: cuint32
     getExecPath1(nil, size)
@@ -1450,7 +1578,7 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} =
     elif defined(solaris):
       result = getApplAux("/proc/" & $getpid() & "/path/a.out")
     elif defined(freebsd):
-      result = getApplAux("/proc/" & $getpid() & "/file")
+      result = getApplFreebsd()
     # little heuristic that may work on other POSIX-like systems:
     if result.len == 0:
       result = getApplHeuristic()
diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim
index 56671ee62..3d3a105f0 100644
--- a/lib/pure/ospaths.nim
+++ b/lib/pure/ospaths.nim
@@ -434,8 +434,8 @@ when not declared(getEnv) or defined(nimscript):
     ## On Windows, network paths are considered absolute too.
     when doslike:
       var len = len(path)
-      result = (len > 1 and path[0] in {'/', '\\'}) or
-               (len > 2 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':')
+      result = (len > 0 and 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] != ':'
     elif defined(RISCOS):
diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim
index 6c2debb1b..76bd2dfe1 100644
--- a/lib/pure/osproc.nim
+++ b/lib/pure/osproc.nim
@@ -774,7 +774,10 @@ elif not defined(useNimRtl):
     data.workingDir = workingDir
 
     when useProcessAuxSpawn:
+      var currentDir = getCurrentDir()
       pid = startProcessAuxSpawn(data)
+      if workingDir.len > 0:
+        setCurrentDir(currentDir)
     else:
       pid = startProcessAuxFork(data)
 
@@ -835,19 +838,22 @@ elif not defined(useNimRtl):
           chck posix_spawn_file_actions_adddup2(fops, data.pStderr[writeIdx], 2)
 
       var res: cint
-      # FIXME: chdir is global to process
       if data.workingDir.len > 0:
         setCurrentDir($data.workingDir)
       var pid: Pid
+      var err: OSErrorCode
 
       if data.optionPoUsePath:
         res = posix_spawnp(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv)
+        if res != 0'i32: err = osLastError()
       else:
         res = posix_spawn(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv)
+        if res != 0'i32: err = osLastError()
 
       discard posix_spawn_file_actions_destroy(fops)
       discard posix_spawnattr_destroy(attr)
-      chck res
+      if res != 0'i32: raiseOSError(err)
+
       return pid
   else:
     proc startProcessAuxFork(data: StartProcessData): Pid =
diff --git a/lib/pure/parsecfg.nim b/lib/pure/parsecfg.nim
index c648b0703..47670cc19 100644
--- a/lib/pure/parsecfg.nim
+++ b/lib/pure/parsecfg.nim
@@ -512,10 +512,16 @@ proc writeConfig*(dict: Config, filename: string) =
             kv = key
           if value != "": ## If the key is not empty
             if not allCharsInSet(value, SymChars):
-              kv.add(segmentChar)
-              kv.add("\"")
-              kv.add(replace(value))
-              kv.add("\"")
+              if find(value, '"') == -1:
+                kv.add(segmentChar)
+                kv.add("\"")
+                kv.add(replace(value))
+                kv.add("\"")
+              else:
+                kv.add(segmentChar)
+                kv.add("\"\"\"")
+                kv.add(replace(value))
+                kv.add("\"\"\"")
             else:
               kv.add(segmentChar)
               kv.add(value)
diff --git a/lib/pure/parsexml.nim b/lib/pure/parsexml.nim
index d16a55302..978c9c516 100644
--- a/lib/pure/parsexml.nim
+++ b/lib/pure/parsexml.nim
@@ -142,9 +142,9 @@ proc kind*(my: XmlParser): XmlEventKind {.inline.} =
 template charData*(my: XmlParser): string =
   ## returns the character data for the events: ``xmlCharData``,
   ## ``xmlWhitespace``, ``xmlComment``, ``xmlCData``, ``xmlSpecial``
-  ## Raises an assertion in debug mode if ``my.kind`` is not one 
+  ## Raises an assertion in debug mode if ``my.kind`` is not one
   ## of those events. In release mode, this will not trigger an error
-  ## but the value returned will not be valid. 
+  ## but the value returned will not be valid.
   assert(my.kind in {xmlCharData, xmlWhitespace, xmlComment, xmlCData,
                      xmlSpecial})
   my.a
@@ -152,49 +152,49 @@ template charData*(my: XmlParser): string =
 template elementName*(my: XmlParser): string =
   ## returns the element name for the events: ``xmlElementStart``,
   ## ``xmlElementEnd``, ``xmlElementOpen``
-  ## Raises an assertion in debug mode if ``my.kind`` is not one 
+  ## Raises an assertion in debug mode if ``my.kind`` is not one
   ## of those events. In release mode, this will not trigger an error
-  ## but the value returned will not be valid. 
+  ## but the value returned will not be valid.
   assert(my.kind in {xmlElementStart, xmlElementEnd, xmlElementOpen})
   my.a
 
 template entityName*(my: XmlParser): string =
   ## returns the entity name for the event: ``xmlEntity``
-  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## Raises an assertion in debug mode if ``my.kind`` is not
   ## ``xmlEntity``. In release mode, this will not trigger an error
-  ## but the value returned will not be valid. 
+  ## but the value returned will not be valid.
   assert(my.kind == xmlEntity)
   my.a
 
 template attrKey*(my: XmlParser): string =
   ## returns the attribute key for the event ``xmlAttribute``
-  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## Raises an assertion in debug mode if ``my.kind`` is not
   ## ``xmlAttribute``. In release mode, this will not trigger an error
-  ## but the value returned will not be valid. 
+  ## but the value returned will not be valid.
   assert(my.kind == xmlAttribute)
   my.a
 
 template attrValue*(my: XmlParser): string =
   ## returns the attribute value for the event ``xmlAttribute``
-  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## Raises an assertion in debug mode if ``my.kind`` is not
   ## ``xmlAttribute``. In release mode, this will not trigger an error
-  ## but the value returned will not be valid. 
+  ## but the value returned will not be valid.
   assert(my.kind == xmlAttribute)
   my.b
 
 template piName*(my: XmlParser): string =
   ## returns the processing instruction name for the event ``xmlPI``
-  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## Raises an assertion in debug mode if ``my.kind`` is not
   ## ``xmlPI``. In release mode, this will not trigger an error
-  ## but the value returned will not be valid. 
+  ## but the value returned will not be valid.
   assert(my.kind == xmlPI)
   my.a
 
 template piRest*(my: XmlParser): string =
   ## returns the rest of the processing instruction for the event ``xmlPI``
-  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## Raises an assertion in debug mode if ``my.kind`` is not
   ## ``xmlPI``. In release mode, this will not trigger an error
-  ## but the value returned will not be valid. 
+  ## but the value returned will not be valid.
   assert(my.kind == xmlPI)
   my.b
 
@@ -572,6 +572,10 @@ proc parseAttribute(my: var XmlParser) =
           inc(pos)
   else:
     markError(my, errQuoteExpected)
+    # error corrections: guess what was meant
+    while buf[pos] != '>' and buf[pos] > ' ':
+      add(my.b, buf[pos])
+      inc pos
   my.bufpos = pos
   parseWhitespace(my, skip=true)
 
@@ -636,12 +640,14 @@ proc rawGetTok(my: var XmlParser) =
 
 proc getTok(my: var XmlParser) =
   while true:
+    let lastKind = my.kind
     rawGetTok(my)
     case my.kind
     of xmlComment:
       if my.options.contains(reportComments): break
     of xmlWhitespace:
-      if my.options.contains(reportWhitespace): break
+      if my.options.contains(reportWhitespace) or lastKind in {xmlCharData, xmlComment, xmlEntity}:
+        break
     else: break
 
 proc next*(my: var XmlParser) =
diff --git a/lib/pure/random.nim b/lib/pure/random.nim
index 08da771dc..955a70143 100644
--- a/lib/pure/random.nim
+++ b/lib/pure/random.nim
@@ -122,7 +122,7 @@ when isMainModule:
       inc occur[x]
     for i, oc in occur:
       if oc < 69:
-        doAssert false, "too few occurances of " & $i
+        doAssert false, "too few occurrences of " & $i
       elif oc > 130:
-        doAssert false, "too many occurances of " & $i
+        doAssert false, "too many occurrences of " & $i
   main()
diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim
index bfc32bc71..14877eb4d 100644
--- a/lib/pure/strutils.nim
+++ b/lib/pure/strutils.nim
@@ -179,9 +179,10 @@ proc isLowerAscii*(s: string): bool {.noSideEffect, procvar,
   if s.len() == 0:
     return false
 
-  result = true
   for c in s:
-    result = c.isLowerAscii() and result
+    if not c.isLowerAscii():
+      return false
+  true
 
 proc isUpperAscii*(s: string): bool {.noSideEffect, procvar,
   rtl, extern: "nsuIsUpperAsciiStr".}=
@@ -193,9 +194,10 @@ proc isUpperAscii*(s: string): bool {.noSideEffect, procvar,
   if s.len() == 0:
     return false
 
-  result = true
   for c in s:
-    result = c.isUpperAscii() and result
+    if not c.isUpperAscii():
+      return false
+  true
 
 proc toLowerAscii*(c: char): char {.noSideEffect, procvar,
   rtl, extern: "nsuToLowerAsciiChar".} =
@@ -1307,10 +1309,12 @@ proc join*[T: not string](a: openArray[T], sep: string = ""): string {.
 type
   SkipTable = array[char, int]
 
+{.push profiler: off.}
 proc preprocessSub(sub: string, a: var SkipTable) =
   var m = len(sub)
   for i in 0..0xff: a[chr(i)] = m+1
   for i in 0..m-1: a[sub[i]] = m-i
+{.pop.}
 
 proc findAux(s, sub: string, start: int, a: SkipTable): int =
   # Fast "quick search" algorithm:
diff --git a/lib/pure/times.nim b/lib/pure/times.nim
index b78a2b966..1767a37be 100644
--- a/lib/pure/times.nim
+++ b/lib/pure/times.nim
@@ -66,12 +66,6 @@ when defined(posix) and not defined(JS):
 
   when not defined(freebsd) and not defined(netbsd) and not defined(openbsd):
     var timezone {.importc, header: "<time.h>".}: int
-  var
-    tzname {.importc, header: "<time.h>" .}: array[0..1, cstring]
-  # we also need tzset() to make sure that tzname is initialized
-  proc tzset() {.importc, header: "<time.h>".}
-  # calling tzset() implicitly to initialize tzname data.
-  tzset()
 
 elif defined(windows):
   import winlean
@@ -82,12 +76,10 @@ elif defined(windows):
     # visual c's c runtime exposes these under a different name
     var
       timezone {.importc: "_timezone", header: "<time.h>".}: int
-      tzname {.importc: "_tzname", header: "<time.h>"}: array[0..1, cstring]
   else:
-    type TimeImpl {.importc: "time_t", header: "<time.h>".} = int32
+    type TimeImpl {.importc: "time_t", header: "<time.h>".} = int
     var
       timezone {.importc, header: "<time.h>".}: int
-      tzname {.importc, header: "<time.h>" .}: array[0..1, cstring]
 
   type
     Time* = distinct TimeImpl
@@ -104,7 +96,7 @@ elif defined(JS):
       getMinutes: proc (): int {.tags: [], raises: [], benign.}
       getMonth: proc (): int {.tags: [], raises: [], benign.}
       getSeconds: proc (): int {.tags: [], raises: [], benign.}
-      getTime: proc (): int {.tags: [], raises: [], benign.}
+      getTime: proc (): int {.tags: [], raises: [], noSideEffect, benign.}
       getTimezoneOffset: proc (): int {.tags: [], raises: [], benign.}
       getDate: proc (): int {.tags: [], raises: [], benign.}
       getUTCDate: proc (): int {.tags: [], raises: [], benign.}
@@ -152,11 +144,14 @@ type
     yearday*: range[0..365]   ## The number of days since January 1,
                               ## in the range 0 to 365.
                               ## Always 0 if the target is JS.
-    isDST*: bool              ## Determines whether DST is in effect. Always
-                              ## ``False`` if time is UTC.
-    tzname*: string           ## The timezone this time is in. E.g. GMT
+    isDST*: bool              ## Determines whether DST is in effect.
+                              ## Semantically, this adds another negative hour
+                              ## offset to the time in addition to the timezone.
     timezone*: int            ## The offset of the (non-DST) timezone in seconds
-                              ## west of UTC.
+                              ## west of UTC. Note that the sign of this number
+                              ## is the opposite of the one in a formatted
+                              ## timezone string like ``+01:00`` (which would be
+                              ## parsed into the timezone ``-3600``).
 
   ## I make some assumptions about the data in here. Either
   ## everything should be positive or everything negative. Zero is
@@ -184,7 +179,8 @@ proc getGMTime*(t: Time): TimeInfo {.tags: [TimeEffect], raises: [], benign.}
   ## converts the calendar time `t` to broken-down time representation,
   ## expressed in Coordinated Universal Time (UTC).
 
-proc timeInfoToTime*(timeInfo: TimeInfo): Time {.tags: [], benign, deprecated.}
+proc timeInfoToTime*(timeInfo: TimeInfo): Time
+    {.tags: [TimeEffect], benign, deprecated.}
   ## converts a broken-down time structure to
   ## calendar time representation. The function ignores the specified
   ## contents of the structure members `weekday` and `yearday` and recomputes
@@ -193,7 +189,7 @@ proc timeInfoToTime*(timeInfo: TimeInfo): Time {.tags: [], benign, deprecated.}
   ## **Warning:** This procedure is deprecated since version 0.14.0.
   ## Use ``toTime`` instead.
 
-proc toTime*(timeInfo: TimeInfo): Time {.tags: [], benign.}
+proc toTime*(timeInfo: TimeInfo): Time {.tags: [TimeEffect], benign.}
   ## converts a broken-down time structure to
   ## calendar time representation. The function ignores the specified
   ## contents of the structure members `weekday` and `yearday` and recomputes
@@ -211,36 +207,25 @@ proc fromSeconds*(since1970: int64): Time {.tags: [], raises: [], benign.} =
 proc toSeconds*(time: Time): float {.tags: [], raises: [], benign.}
   ## Returns the time in seconds since the unix epoch.
 
-proc `$` *(timeInfo: TimeInfo): string {.tags: [], raises: [], benign.}
-  ## converts a `TimeInfo` object to a string representation.
-proc `$` *(time: Time): string {.tags: [], raises: [], benign.}
-  ## converts a calendar time to a string representation.
-
 proc `-`*(a, b: Time): int64 {.
-  rtl, extern: "ntDiffTime", tags: [], raises: [], benign.}
+  rtl, extern: "ntDiffTime", tags: [], raises: [], noSideEffect, benign.}
   ## computes the difference of two calendar times. Result is in seconds.
 
 proc `<`*(a, b: Time): bool {.
-  rtl, extern: "ntLtTime", tags: [], raises: [].} =
+  rtl, extern: "ntLtTime", tags: [], raises: [], noSideEffect.} =
   ## returns true iff ``a < b``, that is iff a happened before b.
   result = a - b < 0
 
 proc `<=` * (a, b: Time): bool {.
-  rtl, extern: "ntLeTime", tags: [], raises: [].}=
+  rtl, extern: "ntLeTime", tags: [], raises: [], noSideEffect.}=
   ## returns true iff ``a <= b``.
   result = a - b <= 0
 
 proc `==`*(a, b: Time): bool {.
-  rtl, extern: "ntEqTime", tags: [], raises: [].} =
+  rtl, extern: "ntEqTime", tags: [], raises: [], noSideEffect.} =
   ## returns true if ``a == b``, that is if both times represent the same value
   result = a - b == 0
 
-when not defined(JS):
-  proc getTzname*(): tuple[nonDST, DST: string] {.tags: [TimeEffect], raises: [],
-    benign.}
-    ## returns the local timezone; ``nonDST`` is the name of the local non-DST
-    ## timezone, ``DST`` is the name of the local DST timezone.
-
 proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign.}
   ## returns the offset of the local (non-DST) timezone in seconds west of UTC.
 
@@ -266,13 +251,13 @@ proc initInterval*(milliseconds, seconds, minutes, hours, days, months,
   result.milliseconds = `mod`(milliseconds, 1000)
   carryO = `div`(milliseconds, 1000)
   result.seconds = `mod`(carryO + seconds, 60)
-  carryO = `div`(seconds, 60)
+  carryO = `div`(carryO + seconds, 60)
   result.minutes = `mod`(carryO + minutes, 60)
-  carryO = `div`(minutes, 60)
+  carryO = `div`(carryO + minutes, 60)
   result.hours = `mod`(carryO + hours, 24)
-  carryO = `div`(hours, 24)
+  carryO = `div`(carryO + hours, 24)
   result.days = carryO + days
-  carryO = 0
+
   result.months = `mod`(months, 12)
   carryO = `div`(months, 12)
   result.years = carryO + years
@@ -283,27 +268,31 @@ proc `+`*(ti1, ti2: TimeInterval): TimeInterval =
   result.milliseconds = `mod`(ti1.milliseconds + ti2.milliseconds, 1000)
   carryO = `div`(ti1.milliseconds + ti2.milliseconds, 1000)
   result.seconds = `mod`(carryO + ti1.seconds + ti2.seconds, 60)
-  carryO = `div`(ti1.seconds + ti2.seconds, 60)
+  carryO = `div`(carryO + ti1.seconds + ti2.seconds, 60)
   result.minutes = `mod`(carryO + ti1.minutes + ti2.minutes, 60)
-  carryO = `div`(ti1.minutes + ti2.minutes, 60)
+  carryO = `div`(carryO + ti1.minutes + ti2.minutes, 60)
   result.hours = `mod`(carryO + ti1.hours + ti2.hours, 24)
-  carryO = `div`(ti1.hours + ti2.hours, 24)
+  carryO = `div`(carryO + ti1.hours + ti2.hours, 24)
   result.days = carryO + ti1.days + ti2.days
-  carryO = 0
+
   result.months = `mod`(ti1.months + ti2.months, 12)
   carryO = `div`(ti1.months + ti2.months, 12)
   result.years = carryO + ti1.years + ti2.years
 
+proc `-`*(ti: TimeInterval): TimeInterval =
+  result = TimeInterval(
+    milliseconds: -ti.milliseconds,
+    seconds: -ti.seconds,
+    minutes: -ti.minutes,
+    hours: -ti.hours,
+    days: -ti.days,
+    months: -ti.months,
+    years: -ti.years
+  )
+
 proc `-`*(ti1, ti2: TimeInterval): TimeInterval =
   ## Subtracts TimeInterval ``ti1`` from ``ti2``.
-  result = ti1
-  result.milliseconds -= ti2.milliseconds
-  result.seconds -= ti2.seconds
-  result.minutes -= ti2.minutes
-  result.hours -= ti2.hours
-  result.days -= ti2.days
-  result.months -= ti2.months
-  result.years -= ti2.years
+  result = ti1 + (-ti2)
 
 proc isLeapYear*(year: int): bool =
   ## returns true if ``year`` is a leap year
@@ -369,7 +358,7 @@ proc `+`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
   ## very accurate.
   let t = toSeconds(toTime(a))
   let secs = toSeconds(a, interval)
-  if a.tzname == "UTC":
+  if a.timezone == 0:
     result = getGMTime(fromSeconds(t + secs))
   else:
     result = getLocalTime(fromSeconds(t + secs))
@@ -379,17 +368,10 @@ proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
   ##
   ## **Note:** This has been only briefly tested, it is inaccurate especially
   ## when you subtract so much that you reach the Julian calendar.
-  let t = toSeconds(toTime(a))
-  var intval: TimeInterval
-  intval.milliseconds = - interval.milliseconds
-  intval.seconds = - interval.seconds
-  intval.minutes = - interval.minutes
-  intval.hours = - interval.hours
-  intval.days = - interval.days
-  intval.months = - interval.months
-  intval.years = - interval.years
-  let secs = toSeconds(a, intval)
-  if a.tzname == "UTC":
+  let
+    t = toSeconds(toTime(a))
+    secs = toSeconds(a, -interval)
+  if a.timezone == 0:
     result = getGMTime(fromSeconds(t + secs))
   else:
     result = getLocalTime(fromSeconds(t + secs))
@@ -424,7 +406,8 @@ when not defined(JS):
 
 when not defined(JS):
   # C wrapper:
-  when defined(freebsd) or defined(netbsd) or defined(openbsd):
+  when defined(freebsd) or defined(netbsd) or defined(openbsd) or
+      defined(macosx):
     type
       StructTM {.importc: "struct tm", final.} = object
         second {.importc: "tm_sec".},
@@ -461,12 +444,6 @@ when not defined(JS):
     importc: "time", header: "<time.h>", tags: [].}
   proc mktime(t: StructTM): Time {.
     importc: "mktime", header: "<time.h>", tags: [].}
-  proc asctime(tblock: StructTM): cstring {.
-    importc: "asctime", header: "<time.h>", tags: [].}
-  proc ctime(time: ptr Time): cstring {.
-    importc: "ctime", header: "<time.h>", tags: [].}
-  #  strftime(s: CString, maxsize: int, fmt: CString, t: tm): int {.
-  #    importc: "strftime", header: "<time.h>".}
   proc getClock(): Clock {.importc: "clock", header: "<time.h>", tags: [TimeEffect].}
   proc difftime(a, b: Time): float {.importc: "difftime", header: "<time.h>",
     tags: [].}
@@ -479,46 +456,17 @@ when not defined(JS):
     const
       weekDays: array[0..6, WeekDay] = [
         dSun, dMon, dTue, dWed, dThu, dFri, dSat]
-    when defined(freebsd) or defined(netbsd) or defined(openbsd):
-      TimeInfo(second: int(tm.second),
-        minute: int(tm.minute),
-        hour: int(tm.hour),
-        monthday: int(tm.monthday),
-        month: Month(tm.month),
-        year: tm.year + 1900'i32,
-        weekday: weekDays[int(tm.weekday)],
-        yearday: int(tm.yearday),
-        isDST: tm.isdst > 0,
-        tzname: if local:
-            if tm.isdst > 0:
-              getTzname().DST
-            else:
-              getTzname().nonDST
-          else:
-            "UTC",
-        # BSD stores in `gmtoff` offset east of UTC in seconds,
-        # but posix systems using west of UTC in seconds
-        timezone: if local: -(tm.gmtoff) else: 0
-      )
-    else:
-      TimeInfo(second: int(tm.second),
-        minute: int(tm.minute),
-        hour: int(tm.hour),
-        monthday: int(tm.monthday),
-        month: Month(tm.month),
-        year: tm.year + 1900'i32,
-        weekday: weekDays[int(tm.weekday)],
-        yearday: int(tm.yearday),
-        isDST: tm.isdst > 0,
-        tzname: if local:
-            if tm.isdst > 0:
-              getTzname().DST
-            else:
-              getTzname().nonDST
-          else:
-            "UTC",
-        timezone: if local: getTimezone() else: 0
-      )
+    TimeInfo(second: int(tm.second),
+      minute: int(tm.minute),
+      hour: int(tm.hour),
+      monthday: int(tm.monthday),
+      month: Month(tm.month),
+      year: tm.year + 1900'i32,
+      weekday: weekDays[int(tm.weekday)],
+      yearday: int(tm.yearday),
+      isDST: tm.isdst > 0,
+      timezone: if local: getTimezone() else: 0
+    )
 
 
   proc timeInfoToTM(t: TimeInfo): StructTM =
@@ -569,29 +517,18 @@ when not defined(JS):
   proc timeInfoToTime(timeInfo: TimeInfo): Time =
     var cTimeInfo = timeInfo # for C++ we have to make a copy,
     # because the header of mktime is broken in my version of libc
-    return mktime(timeInfoToTM(cTimeInfo))
+    result = mktime(timeInfoToTM(cTimeInfo))
+    # mktime is defined to interpret the input as local time. As timeInfoToTM
+    # does ignore the timezone, we need to adjust this here.
+    result = Time(TimeImpl(result) - getTimezone() + timeInfo.timezone)
 
   proc toTime(timeInfo: TimeInfo): Time =
     var cTimeInfo = timeInfo # for C++ we have to make a copy,
     # because the header of mktime is broken in my version of libc
-    return mktime(timeInfoToTM(cTimeInfo))
-
-  proc toStringTillNL(p: cstring): string =
-    result = ""
-    var i = 0
-    while p[i] != '\0' and p[i] != '\10' and p[i] != '\13':
-      add(result, p[i])
-      inc(i)
-
-  proc `$`(timeInfo: TimeInfo): string =
-    # BUGFIX: asctime returns a newline at the end!
-    var p = asctime(timeInfoToTM(timeInfo))
-    result = toStringTillNL(p)
-
-  proc `$`(time: Time): string =
-    # BUGFIX: ctime returns a newline at the end!
-    var a = time
-    return toStringTillNL(ctime(addr(a)))
+    result = mktime(timeInfoToTM(cTimeInfo))
+    # mktime is defined to interpret the input as local time. As timeInfoToTM
+    # does ignore the timezone, we need to adjust this here.
+    result = Time(TimeImpl(result) - getTimezone() + timeInfo.timezone)
 
   const
     epochDiff = 116444736000000000'i64
@@ -605,9 +542,6 @@ when not defined(JS):
     ## converts a Windows time to a UNIX `Time` (``time_t``)
     result = Time((t - epochDiff) div rateDiff)
 
-  proc getTzname(): tuple[nonDST, DST: string] =
-    return ($tzname[0], $tzname[1])
-
   proc getTimezone(): int =
     when defined(freebsd) or defined(netbsd) or defined(openbsd):
       var a = timec(nil)
@@ -675,26 +609,16 @@ elif defined(JS):
     result.weekday = weekDays[t.getUTCDay()]
     result.yearday = 0
 
-  proc timeInfoToTime*(timeInfo: TimeInfo): Time =
-    result = internGetTime()
-    result.setSeconds(timeInfo.second)
-    result.setMinutes(timeInfo.minute)
-    result.setHours(timeInfo.hour)
-    result.setMonth(ord(timeInfo.month))
-    result.setFullYear(timeInfo.year)
-    result.setDate(timeInfo.monthday)
+  proc timeInfoToTime*(timeInfo: TimeInfo): Time = toTime(timeInfo)
 
   proc toTime*(timeInfo: TimeInfo): Time =
     result = internGetTime()
-    result.setSeconds(timeInfo.second)
     result.setMinutes(timeInfo.minute)
     result.setHours(timeInfo.hour)
     result.setMonth(ord(timeInfo.month))
     result.setFullYear(timeInfo.year)
     result.setDate(timeInfo.monthday)
-
-  proc `$`(timeInfo: TimeInfo): string = return $(toTime(timeInfo))
-  proc `$`(time: Time): string = return $time.toLocaleString()
+    result.setSeconds(timeInfo.second + timeInfo.timezone)
 
   proc `-` (a, b: Time): int64 =
     return a.getTime() - b.getTime()
@@ -802,6 +726,13 @@ proc `-`*(t: Time, ti: TimeInterval): Time =
   ## ``echo getTime() - 1.day``
   result = toTime(getLocalTime(t) - ti)
 
+const
+  secondsInMin = 60
+  secondsInHour = 60*60
+  secondsInDay = 60*60*24
+  minutesInHour = 60
+  epochStartYear = 1970
+
 proc formatToken(info: TimeInfo, token: string, buf: var string) =
   ## Helper of the format proc to parse individual tokens.
   ##
@@ -891,24 +822,32 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) =
     if fyear.len != 5: fyear = repeat('0', 5-fyear.len()) & fyear
     buf.add(fyear)
   of "z":
-    let hrs = (info.timezone div 60) div 60
-    buf.add($hrs)
+    let
+      nonDstTz = info.timezone - int(info.isDst) * secondsInHour
+      hours = abs(nonDstTz) div secondsInHour
+    if nonDstTz <= 0: buf.add('+')
+    else: buf.add('-')
+    buf.add($hours)
   of "zz":
-    let hrs = (info.timezone div 60) div 60
-
-    buf.add($hrs)
-    if hrs.abs < 10:
-      var atIndex = buf.len-(($hrs).len-(if hrs < 0: 1 else: 0))
-      buf.insert("0", atIndex)
+    let
+      nonDstTz = info.timezone - int(info.isDst) * secondsInHour
+      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 hrs = (info.timezone div 60) div 60
-
-    buf.add($hrs & ":00")
-    if hrs.abs < 10:
-      var atIndex = buf.len-(($hrs & ":00").len-(if hrs < 0: 1 else: 0))
-      buf.insert("0", atIndex)
-  of "ZZZ":
-    buf.add(info.tzname)
+    let
+      nonDstTz = info.timezone - int(info.isDst) * secondsInHour
+      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 "":
     discard
   else:
@@ -945,8 +884,7 @@ proc format*(info: TimeInfo, f: string): string =
   ##    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 ``:00``.                                                    ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00``
-  ##    ZZZ      Displays the name of the timezone.                                                 ``GMT -> GMT``, ``EST -> EST``
+  ##    zzz      Same as above but with ``:mm`` where *mm* represents minutes.                      ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00``
   ## ==========  =================================================================================  ================================================
   ##
   ## Other strings can be inserted by putting them in ``''``. For example
@@ -984,6 +922,18 @@ proc format*(info: TimeInfo, f: string): string =
 
     inc(i)
 
+proc `$`*(timeInfo: TimeInfo): string {.tags: [], raises: [], benign.} =
+  ## converts a `TimeInfo` object to a string representation.
+  ## It uses the format ``yyyy-MM-dd'T'HH-mm-sszzz``.
+  try:
+    result = format(timeInfo, "yyyy-MM-dd'T'HH:mm:sszzz") # todo: optimize this
+  except ValueError: assert false # cannot happen because format string is valid
+
+proc `$`*(time: Time): string {.tags: [TimeEffect], raises: [], benign.} =
+  ## converts a `Time` value to a string representation. It will use the local
+  ## time zone and use the format ``yyyy-MM-dd'T'HH-mm-sszzz``.
+  $getLocalTime(time)
+
 {.pop.}
 
 proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
@@ -1052,7 +1002,6 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
   of "M":
     var pd = parseInt(value[j..j+1], sv)
     info.month = Month(sv-1)
-    info.monthday = sv
     j += pd
   of "MM":
     var month = value[j..j+1].parseInt()
@@ -1141,42 +1090,59 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
     info.year = value[j..j+3].parseInt()
     j += 4
   of "z":
+    info.isDST = false
     if value[j] == '+':
-      info.timezone = parseInt($value[j+1])
+      info.timezone = 0 - parseInt($value[j+1]) * secondsInHour
     elif value[j] == '-':
-      info.timezone = 0-parseInt($value[j+1])
+      info.timezone = parseInt($value[j+1]) * secondsInHour
+    elif value[j] == 'Z':
+      info.timezone = 0
+      j += 1
+      return
     else:
       raise newException(ValueError,
         "Couldn't parse timezone offset (z), got: " & value[j])
     j += 2
   of "zz":
+    info.isDST = false
     if value[j] == '+':
-      info.timezone = value[j+1..j+2].parseInt()
+      info.timezone = 0 - value[j+1..j+2].parseInt() * secondsInHour
     elif value[j] == '-':
-      info.timezone = 0-value[j+1..j+2].parseInt()
+      info.timezone = value[j+1..j+2].parseInt() * secondsInHour
+    elif value[j] == 'Z':
+      info.timezone = 0
+      j += 1
+      return
     else:
       raise newException(ValueError,
         "Couldn't parse timezone offset (zz), got: " & value[j])
     j += 3
   of "zzz":
-    if value[j] == '+':
-      info.timezone = value[j+1..j+2].parseInt()
-    elif value[j] == '-':
-      info.timezone = 0-value[j+1..j+2].parseInt()
+    info.isDST = false
+    var factor = 0
+    if value[j] == '+': factor = -1
+    elif value[j] == '-': factor = 1
+    elif value[j] == 'Z':
+      info.timezone = 0
+      j += 1
+      return
     else:
       raise newException(ValueError,
         "Couldn't parse timezone offset (zzz), got: " & value[j])
-    j += 6
-  of "ZZZ":
-    info.tzname = value[j..j+2].toUpperAscii()
-    j += 3
+    info.timezone = factor * value[j+1..j+2].parseInt() * secondsInHour
+    j += 4
+    info.timezone += factor * value[j..j+1].parseInt() * 60
+    j += 2
   else:
     # Ignore the token and move forward in the value string by the same length
     j += token.len
 
 proc parse*(value, layout: string): TimeInfo =
-  ## This function parses a date/time string using the standard format identifiers (below)
-  ## The function defaults information not provided in the format string from the running program (timezone, month, year, etc)
+  ## This function parses a date/time string using the standard format
+  ## identifiers as listed below. The function defaults information not provided
+  ## in the format string from the running program (timezone, month, year, etc).
+  ## Daylight saving time is only set if no timezone is given and the given date
+  ## lies within the DST period of the current locale.
   ##
   ## ==========  =================================================================================  ================================================
   ## Specifier   Description                                                                        Example
@@ -1201,10 +1167,9 @@ proc parse*(value, layout: string): TimeInfo =
   ##    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.                                             ``GMT+7 -> +7``, ``GMT-5 -> -5``
+  ##    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 ``:00``.                                                    ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00``
-  ##    ZZZ      Displays the name of the timezone.                                                 ``GMT -> GMT``, ``EST -> EST``
+  ##    zzz      Same as above but with ``:mm`` where *mm* represents minutes.                      ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00``
   ## ==========  =================================================================================  ================================================
   ##
   ## Other strings can be inserted by putting them in ``''``. For example
@@ -1220,6 +1185,8 @@ proc parse*(value, layout: string): TimeInfo =
   info.hour = 0
   info.minute = 0
   info.second = 0
+  info.isDST = true # using this is flag for checking whether a timezone has \
+      # been read (because DST is always false when a tz is parsed)
   while true:
     case layout[i]
     of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',':
@@ -1248,12 +1215,19 @@ proc parse*(value, layout: string): TimeInfo =
       else:
         parseToken(info, token, value, j)
         token = ""
-  # Reset weekday (might not have been provided and the default may be wrong)
-  # and yearday (is never provided directly and therefore probably wrong)
-  let processed = getLocalTime(toTime(info))
-  info.weekday = processed.weekday
-  info.yearday = processed.yearday
-  return info
+
+  if info.isDST:
+    # means that no timezone has been parsed. In this case, we need to check
+    # whether the date is within DST of the local time.
+    let tmp = getLocalTime(toTime(info))
+    # correctly set isDST so that the following step works on the correct time
+    info.isDST = tmp.isDST
+
+  # Correct weekday and yearday; transform timestamp to local time.
+  # There currently is no way of returning this with the original (parsed)
+  # timezone while also setting weekday and yearday (we are depending on stdlib
+  # to provide this calculation).
+  return getLocalTime(toTime(info))
 
 # Leap year calculations are adapted from:
 # http://www.codeproject.com/Articles/7358/Ultra-fast-Algorithms-for-Working-with-Leap-Years
@@ -1284,12 +1258,6 @@ proc countYearsAndDays*(daySpan: int): tuple[years: int, days: int] =
   result.years = days div 365
   result.days = days mod 365
 
-const
-  secondsInMin = 60
-  secondsInHour = 60*60
-  secondsInDay = 60*60*24
-  epochStartYear = 1970
-
 proc getDayOfWeek*(day, month, year: int): WeekDay =
   ## Returns the day of the week enum from day, month and year.
   # Day & month start from one.
diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim
index 0fc2e441e..cdca02ed7 100644
--- a/lib/pure/unittest.nim
+++ b/lib/pure/unittest.nim
@@ -11,11 +11,21 @@
 ##
 ## This module implements boilerplate to make unit testing easy.
 ##
+## The test status and name is printed after any output or traceback.
+##
 ## Example:
 ##
 ## .. code:: nim
 ##
 ##   suite "description for this stuff":
+##     echo "suite setup: run once before the tests"
+##
+##     setup:
+##       echo "run before each test"
+##
+##     teardown:
+##       echo "run after each test":
+##
 ##     test "essential truths":
 ##       # give up and stop if this fails
 ##       require(true)
@@ -30,6 +40,13 @@
 ##       let v = @[1, 2, 3]  # you can do initialization here
 ##       expect(IndexError):
 ##         discard v[4]
+##
+##     echo "suite teardown: run once after the tests"
+##
+##
+## Tests can be nested, however failure of a nested test will not mark the
+## parent test as failed. Setup and teardown are inherited. Setup can be
+## overridden locally.
 
 import
   macros
@@ -81,7 +98,7 @@ proc startSuite(name: string) =
   template rawPrint() = echo("\n[Suite] ", name) 
   when not defined(ECMAScript):
     if colorOutput:
-      styledEcho styleBright, fgBlue, "\n[Suite] ", fgWhite, name
+      styledEcho styleBright, fgBlue, "\n[Suite] ", resetStyle, name
     else: rawPrint()
   else: rawPrint()
 
@@ -142,7 +159,7 @@ proc testDone(name: string, s: TestStatus, indent: bool) =
                     of FAILED: fgRed
                     of SKIPPED: fgYellow
                     else: fgWhite
-        styledEcho styleBright, color, prefix, "[", $s, "] ", fgWhite, name
+        styledEcho styleBright, color, prefix, "[", $s, "] ", resetStyle, name
       else:
         rawPrint()
     else:
@@ -226,10 +243,11 @@ template fail* =
   checkpoints = @[]
 
 template skip* =
-  ## Makes test to be skipped. Should be used directly
+  ## Mark the test as skipped. Should be used directly
   ## in case when it is not possible to perform test
   ## for reasons depending on outer environment,
   ## or certain application logic conditions or configurations.
+  ## The test code is still executed.
   ##
   ## .. code-block:: nim
   ##
@@ -250,12 +268,12 @@ macro check*(conditions: untyped): untyped =
   ##
   ##  import strutils
   ##
-  ##  check("AKB48".toLower() == "akb48")
+  ##  check("AKB48".toLowerAscii() == "akb48")
   ##
   ##  let teams = {'A', 'K', 'B', '4', '8'}
   ##
   ##  check:
-  ##    "AKB48".toLower() == "akb48"
+  ##    "AKB48".toLowerAscii() == "akb48"
   ##    'C' in teams
   let checked = callsite()[1]
   var
diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim
index f0a3796a5..e7db39c10 100644
--- a/lib/pure/uri.nim
+++ b/lib/pure/uri.nim
@@ -264,6 +264,11 @@ proc `/`*(x: Uri, path: string): Uri =
   ##   let bar = parseUri("http://example.com/foo/bar/") / "baz"
   ##   assert bar.path == "/foo/bar/baz"
   result = x
+
+  if result.path.len == 0:
+    result.path = path
+    return
+
   if result.path[result.path.len-1] == '/':
     if path[0] == '/':
       result.path.add(path[1 .. path.len-1])
@@ -442,3 +447,8 @@ when isMainModule:
   # bug #3207
   block:
     doAssert parseUri("http://qq/1").combine(parseUri("https://qqq")).`$` == "https://qqq"
+
+  # bug #4959
+  block:
+    let foo = parseUri("http://example.com") / "/baz"
+    doAssert foo.path == "/baz"
diff --git a/lib/pure/xmlparser.nim b/lib/pure/xmlparser.nim
index 2a2c3e1dd..22bd259b7 100644
--- a/lib/pure/xmlparser.nim
+++ b/lib/pure/xmlparser.nim
@@ -28,7 +28,7 @@ proc raiseInvalidXml(errors: seq[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 untilElementEnd(x: var XmlParser, result: XmlNode,
                      errors: var seq[string]) =
@@ -164,3 +164,6 @@ when isMainModule:
       var xml = loadXml(filePath, errors)
       assert(errors.len == 0, "The file tests/testdata/doc1.xml should be parsed without errors.")
 
+    block bug1518:
+      var err: seq[string] = @[]
+      assert $parsexml(newStringStream"<tag>One &amp; two</tag>", "temp.xml", err) == "<tag>One &amp; two</tag>"
diff --git a/lib/stdlib.nimble b/lib/stdlib.nimble
index bd1b78efe..6949bfcc4 100644
--- a/lib/stdlib.nimble
+++ b/lib/stdlib.nimble
@@ -1,6 +1,6 @@
 [Package]
 name          = "stdlib"
-version       = "0.15.1"
+version       = "0.15.3"
 author        = "Dominik Picheta"
 description   = "Nim's standard library."
 license       = "MIT"
diff --git a/lib/system.nim b/lib/system.nim
index 4c8af3d51..69d3db291 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -1272,12 +1272,14 @@ const
 
   seqShallowFlag = low(int)
 
+{.push profiler: off.}
 when defined(nimKnowsNimvm):
   let nimvm* {.magic: "Nimvm".}: bool = false
     ## may be used only in "when" expression.
     ## It is true in Nim VM context and false otherwise
 else:
   const nimvm*: bool = false
+{.pop.}
 
 proc compileOption*(option: string): bool {.
   magic: "CompileOption", noSideEffect.}
@@ -1827,7 +1829,7 @@ const
   NimMinor*: int = 15
     ## is the minor number of Nim's version.
 
-  NimPatch*: int = 1
+  NimPatch*: int = 3
     ## is the patch number of Nim's version.
 
   NimVersion*: string = $NimMajor & "." & $NimMinor & "." & $NimPatch
@@ -2176,25 +2178,34 @@ proc `&` *[T](x: T, y: seq[T]): seq[T] {.noSideEffect.} =
   for i in 0..y.len-1:
     result[i+1] = y[i]
 
-when not defined(nimscript):
-  when not defined(JS) or defined(nimphp):
-    proc seqToPtr[T](x: seq[T]): pointer {.inline, nosideeffect.} =
-      result = cast[pointer](x)
+proc `==` *[T](x, y: seq[T]): bool {.noSideEffect.} =
+  ## Generic equals operator for sequences: relies on a equals operator for
+  ## the element type `T`.
+  when nimvm:
+    if x.isNil and y.isNil:
+      return true
   else:
-    proc seqToPtr[T](x: seq[T]): pointer {.asmNoStackFrame, nosideeffect.} =
-      asm """return `x`"""
+    when not defined(JS) or defined(nimphp):
+      proc seqToPtr[T](x: seq[T]): pointer {.inline, nosideeffect.} =
+        result = cast[pointer](x)
+    else:
+      proc seqToPtr[T](x: seq[T]): pointer {.asmNoStackFrame, nosideeffect.} =
+        asm """return `x`"""
 
-  proc `==` *[T](x, y: seq[T]): bool {.noSideEffect.} =
-    ## Generic equals operator for sequences: relies on a equals operator for
-    ## the element type `T`.
     if seqToPtr(x) == seqToPtr(y):
-      result = true
-    elif seqToPtr(x) == nil or seqToPtr(y) == nil:
-      result = false
-    elif x.len == y.len:
-      for i in 0..x.len-1:
-        if x[i] != y[i]: return false
-      result = true
+      return true
+
+  if x.isNil or y.isNil:
+    return false
+
+  if x.len != y.len:
+    return false
+
+  for i in 0..x.len-1:
+    if x[i] != y[i]:
+      return false
+
+  return true
 
 proc find*[T, S](a: T, item: S): int {.inline.}=
   ## Returns the first index of `item` in `a` or -1 if not found. This requires
@@ -2325,7 +2336,11 @@ proc collectionToString[T: set | seq](x: T, b, e: string): string =
   var firstElement = true
   for value in items(x):
     if not firstElement: result.add(", ")
-    result.add($value)
+    when compiles(value.isNil):
+      if value.isNil: result.add "nil"
+      else: result.add($value)
+    else:
+      result.add($value)
     firstElement = false
   result.add(e)
 
@@ -2531,6 +2546,7 @@ when hostOS == "standalone":
   include "$projectpath/panicoverride"
 
 when not declared(sysFatal):
+  {.push profiler: off.}
   when hostOS == "standalone":
     proc sysFatal(exceptn: typedesc, message: string) {.inline.} =
       panic(message)
@@ -2550,6 +2566,7 @@ when not declared(sysFatal):
       new(e)
       e.msg = message & arg
       raise e
+  {.pop.}
 
 proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo", benign.}
   ## get type information for `x`. Ordinary code should not use this, but
@@ -2581,10 +2598,11 @@ else:
 when not defined(JS): #and not defined(nimscript):
   {.push stack_trace: off, profiler:off.}
 
-  when not defined(nimscript) and not defined(nogc):
+  when hasAlloc:
     when not defined(gcStack):
       proc initGC()
-    when not defined(boehmgc) and not defined(useMalloc) and not defined(gogc) and not defined(gcStack):
+    when not defined(boehmgc) and not defined(useMalloc) and
+        not defined(gogc) and not defined(gcStack):
       proc initAllocator() {.inline.}
 
     proc initStackBottom() {.inline, compilerproc.} =
@@ -2602,9 +2620,10 @@ when not defined(JS): #and not defined(nimscript):
       when declared(setStackBottom):
         setStackBottom(locals)
 
-  when hasAlloc:
+    {.push profiler: off.}
     var
       strDesc = TNimType(size: sizeof(string), kind: tyString, flags: {ntfAcyclic})
+    {.pop.}
 
 
   # ----------------- IO Part ------------------------------------------------
@@ -2616,6 +2635,8 @@ when not defined(JS): #and not defined(nimscript):
     FileMode* = enum           ## The file mode when opening a file.
       fmRead,                   ## Open the file for read access only.
       fmWrite,                  ## Open the file for write access only.
+                                ## If the file does not exist, it will be
+                                ## created.
       fmReadWrite,              ## Open the file for read and write access.
                                 ## If the file does not exist, it will be
                                 ## created.
@@ -2632,10 +2653,15 @@ when not defined(JS): #and not defined(nimscript):
 
   include "system/ansi_c"
 
-  when not defined(nimscript):
-    proc cmp(x, y: string): int =
+  proc cmp(x, y: string): int =
+    when nimvm:
+      if x < y: result = -1
+      elif x > y: result = 1
+      else: result = 0
+    else:
       result = int(c_strcmp(x, y))
 
+  when not defined(nimscript):
     proc zeroMem(p: pointer, size: Natural) =
       c_memset(p, 0, size)
     proc copyMem(dest, source: pointer, size: Natural) =
@@ -2645,12 +2671,6 @@ when not defined(JS): #and not defined(nimscript):
     proc equalMem(a, b: pointer, size: Natural): bool =
       c_memcmp(a, b, size) == 0
 
-  else:
-    proc cmp(x, y: string): int =
-      if x < y: result = -1
-      elif x > y: result = 1
-      else: result = 0
-
   when defined(nimscript):
     proc readFile*(filename: string): string {.tags: [ReadIOEffect], benign.}
       ## Opens a file named `filename` for reading, calls `readAll
@@ -2936,6 +2956,8 @@ when not defined(JS): #and not defined(nimscript):
         ## lead to the ``raise`` statement. This only works for debug builds.
 
     {.push stack_trace: off, profiler:off.}
+    when defined(memtracker):
+      include "system/memtracker"
     when hostOS == "standalone":
       include "system/embedded"
     else:
@@ -2978,7 +3000,9 @@ when not defined(JS): #and not defined(nimscript):
       else:
         result = n.sons[n.len]
 
+    {.push profiler:off.}
     when hasAlloc: include "system/mmdisp"
+    {.pop.}
     {.push stack_trace: off, profiler:off.}
     when hasAlloc: include "system/sysstr"
     {.pop.}
@@ -3676,7 +3700,7 @@ template closureScope*(body: untyped): untyped =
 when defined(nimconfig):
   include "system/nimscript"
 
-when defined(windows) and appType == "console" and not defined(nimconfig):
+when defined(windows) and appType == "console" and defined(nimSetUtf8CodePage):
   proc setConsoleOutputCP(codepage: cint): cint {.stdcall, dynlib: "kernel32",
     importc: "SetConsoleOutputCP".}
   discard setConsoleOutputCP(65001) # 65001 - utf-8 codepage
diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim
index 745bbbf62..3a8e8a1b6 100644
--- a/lib/system/alloc.nim
+++ b/lib/system/alloc.nim
@@ -15,6 +15,10 @@
 
 include osalloc
 
+template track(op, address, size) =
+  when defined(memTracker):
+    memTrackerOp(op, address, size)
+
 # We manage *chunks* of memory. Each chunk is a multiple of the page size.
 # Each chunk starts at an address that is divisible by the page size. Chunks
 # that are bigger than ``ChunkOsReturn`` are returned back to the operating
@@ -645,6 +649,7 @@ proc alloc(allocator: var MemRegion, size: Natural): pointer =
   cast[ptr FreeCell](result).zeroField = 1 # mark it as used
   sysAssert(not isAllocatedPtr(allocator, result), "alloc")
   result = cast[pointer](cast[ByteAddress](result) +% sizeof(FreeCell))
+  track("alloc", result, size)
 
 proc alloc0(allocator: var MemRegion, size: Natural): pointer =
   result = alloc(allocator, size)
@@ -658,6 +663,7 @@ proc dealloc(allocator: var MemRegion, p: pointer) =
   sysAssert(cast[ptr FreeCell](x).zeroField == 1, "dealloc 2")
   rawDealloc(allocator, x)
   sysAssert(not isAllocatedPtr(allocator, x), "dealloc 3")
+  track("dealloc", p, 0)
 
 proc realloc(allocator: var MemRegion, p: pointer, newsize: Natural): pointer =
   if newsize > 0:
diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim
index dcf41b67d..d00ab64b1 100644
--- a/lib/system/excpt.nim
+++ b/lib/system/excpt.nim
@@ -339,6 +339,8 @@ when not defined(noSignalHandler):
           action("unknown signal\n")
 
     # print stack trace and quit
+    when defined(memtracker):
+      logPendingOps()
     when hasSomeStackTrace:
       GC_disable()
       var buf = newStringOfCap(2000)
diff --git a/lib/system/gc.nim b/lib/system/gc.nim
index c8623f2f1..7fb4c7ac7 100644
--- a/lib/system/gc.nim
+++ b/lib/system/gc.nim
@@ -471,6 +471,7 @@ proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer =
   # its refcount is zero, so add it to the ZCT:
   addNewObjToZCT(res, gch)
   when logGC: writeCell("new cell", res)
+  track("rawNewObj", res, size)
   gcTrace(res, csAllocated)
   release(gch)
   when useCellIds:
@@ -516,6 +517,7 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} =
   res.refcount = rcIncrement # refcount is 1
   sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3")
   when logGC: writeCell("new cell", res)
+  track("newObjRC1", res, size)
   gcTrace(res, csAllocated)
   release(gch)
   when useCellIds:
@@ -558,6 +560,8 @@ proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer =
     writeCell("growObj new cell", res)
   gcTrace(ol, csZctFreed)
   gcTrace(res, csAllocated)
+  track("growObj old", ol, 0)
+  track("growObj new", res, newsize)
   when reallyDealloc:
     sysAssert(allocInv(gch.region), "growObj before dealloc")
     if ol.refcount shr rcShift <=% 1:
@@ -601,6 +605,7 @@ proc growObj(old: pointer, newsize: int): pointer {.rtl.} =
 proc freeCyclicCell(gch: var GcHeap, c: PCell) =
   prepareDealloc(c)
   gcTrace(c, csCycFreed)
+  track("cycle collector dealloc cell", c, 0)
   when logGC: writeCell("cycle collector dealloc cell", c)
   when reallyDealloc:
     sysAssert(allocInv(gch.region), "free cyclic cell")
@@ -670,6 +675,7 @@ proc doOperation(p: pointer, op: WalkOp) =
     gcAssert(c.refcount >=% rcIncrement, "doOperation 2")
     #c.refcount = c.refcount -% rcIncrement
     when logGC: writeCell("decref (from doOperation)", c)
+    track("waZctDecref", p, 0)
     decRef(c)
     #if c.refcount <% rcIncrement: addZCT(gch.zct, c)
   of waPush:
@@ -762,6 +768,7 @@ proc collectZCT(gch: var GcHeap): bool =
       # In any case, it should be removed from the ZCT. But not
       # freed. **KEEP THIS IN MIND WHEN MAKING THIS INCREMENTAL!**
       when logGC: writeCell("zct dealloc cell", c)
+      track("zct dealloc cell", c, 0)
       gcTrace(c, csZctFreed)
       # We are about to free the object, call the finalizer BEFORE its
       # children are deleted as well, because otherwise the finalizer may
diff --git a/lib/system/gc_stack.nim b/lib/system/gc_stack.nim
index 5f72b8959..3eda08df9 100644
--- a/lib/system/gc_stack.nim
+++ b/lib/system/gc_stack.nim
@@ -79,6 +79,7 @@ template withRegion*(r: MemRegion; body: untyped) =
   try:
     body
   finally:
+    r = tlRegion
     tlRegion = oldRegion
 
 template inc(p: pointer, s: int) =
@@ -464,4 +465,10 @@ proc getFreeMem(): int = tlRegion.remaining
 proc getTotalMem(): int =
   result = tlRegion.totalSize
 
+proc getOccupiedMem*(r: MemRegion): int =
+  result = r.totalSize - r.remaining
+proc getFreeMem*(r: MemRegion): int = r.remaining
+proc getTotalMem*(r: MemRegion): int =
+  result = r.totalSize
+
 proc setStackBottom(theStackBottom: pointer) = discard
diff --git a/lib/system/hti.nim b/lib/system/hti.nim
index 984f888cb..892a209df 100644
--- a/lib/system/hti.nim
+++ b/lib/system/hti.nim
@@ -62,7 +62,6 @@ type
     tyUInt16,
     tyUInt32,
     tyUInt64,
-    tyBigNum,
 
   TNimNodeKind = enum nkNone, nkSlot, nkList, nkCase
   TNimNode {.codegenType.} = object
diff --git a/lib/system/memtracker.nim b/lib/system/memtracker.nim
new file mode 100644
index 000000000..a9767bbca
--- /dev/null
+++ b/lib/system/memtracker.nim
@@ -0,0 +1,70 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2016 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Memory tracking support for Nim.
+
+when not defined(memTracker):
+  {.error: "Memory tracking support is turned off! Enable memory tracking by passing `--memtracker:on` to the compiler (see the Nim Compiler User Guide for more options).".}
+
+when defined(noSignalHandler):
+  {.error: "Memory tracking works better with the default signal handler.".}
+
+# We don't want to memtrack the tracking code ...
+{.push memtracker: off.}
+
+type
+  LogEntry* = object
+    op*: cstring
+    address*: pointer
+    size*: int
+    file*: cstring
+    line*: int
+  TrackLog* = object
+    count*: int
+    disabled: bool
+    data*: array[4000, LogEntry]
+  TrackLogger* = proc (log: TrackLog) {.nimcall, tags: [], locks: 0.}
+
+var
+  gLog*: TrackLog
+  gLogger*: TrackLogger = proc (log: TrackLog) = discard
+
+proc setTrackLogger*(logger: TrackLogger) =
+  gLogger = logger
+
+proc addEntry(entry: LogEntry) =
+  if not gLog.disabled:
+    if gLog.count > high(gLog.data):
+      gLogger(gLog)
+      gLog.count = 0
+    gLog.data[gLog.count] = entry
+    inc gLog.count
+
+proc memTrackerWrite(address: pointer; size: int; file: cstring; line: int) {.compilerProc.} =
+  addEntry LogEntry(op: "write", address: address,
+      size: size, file: file, line: line)
+
+proc memTrackerOp*(op: cstring; address: pointer; size: int) =
+  addEntry LogEntry(op: op, address: address, size: size,
+      file: "", line: 0)
+
+proc memTrackerDisable*() =
+  gLog.disabled = true
+
+proc memTrackerEnable*() =
+  gLog.disabled = false
+
+proc logPendingOps() {.noconv.} =
+  # forward declared and called from Nim's signal handler.
+  gLogger(gLog)
+  gLog.count = 0
+
+addQuitProc logPendingOps
+
+{.pop.}
diff --git a/lib/system/osalloc.nim b/lib/system/osalloc.nim
index 316dd74d7..b0639a75a 100644
--- a/lib/system/osalloc.nim
+++ b/lib/system/osalloc.nim
@@ -81,20 +81,27 @@ elif defined(posix):
   const
     PROT_READ  = 1             # page can be read
     PROT_WRITE = 2             # page can be written
-    MAP_PRIVATE = 2'i32        # Changes are private
 
   when defined(macosx) or defined(bsd):
     const MAP_ANONYMOUS = 0x1000
+    const MAP_PRIVATE = 0x02        # Changes are private
   elif defined(solaris):
     const MAP_ANONYMOUS = 0x100
+    const MAP_PRIVATE = 0x02        # Changes are private
+  elif defined(linux) and defined(amd64):
+    # actually, any architecture using asm-generic, but being conservative here,
+    # some arches like mips and alpha use different values
+    const MAP_ANONYMOUS = 0x20
+    const MAP_PRIVATE = 0x02        # Changes are private
   else:
     var
       MAP_ANONYMOUS {.importc: "MAP_ANONYMOUS", header: "<sys/mman.h>".}: cint
+      MAP_PRIVATE {.importc: "MAP_PRIVATE", header: "<sys/mman.h>".}: cint
 
-  proc mmap(adr: pointer, len: int, prot, flags, fildes: cint,
+  proc mmap(adr: pointer, len: csize, prot, flags, fildes: cint,
             off: int): pointer {.header: "<sys/mman.h>".}
 
-  proc munmap(adr: pointer, len: int): cint {.header: "<sys/mman.h>".}
+  proc munmap(adr: pointer, len: csize): cint {.header: "<sys/mman.h>".}
 
   proc osAllocPages(size: int): pointer {.inline.} =
     result = mmap(nil, size, PROT_READ or PROT_WRITE,
diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim
index 3e9657ce0..5c10392f1 100644
--- a/lib/system/sysio.nim
+++ b/lib/system/sysio.nim
@@ -104,21 +104,22 @@ proc getFileHandle*(f: File): FileHandle = c_fileno(f)
 
 proc readLine(f: File, line: var TaintedString): bool =
   var pos = 0
+  var sp: cint = 80
   # Use the currently reserved space for a first try
-  when defined(nimscript):
-    var space: cint = 80
+  if line.string.isNil:
+    line = TaintedString(newStringOfCap(80))
   else:
-    var space: cint = cint(cast[PGenericSeq](line.string).space)
-  line.string.setLen(space)
-
+    when not defined(nimscript):
+      sp = cint(cast[PGenericSeq](line.string).space)
+    line.string.setLen(sp)
   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, space)
-    if c_fgets(addr line.string[pos], space, f) == nil:
+    c_memset(addr line.string[pos], '\l'.ord, sp)
+    if c_fgets(addr line.string[pos], sp, f) == nil:
       line.string.setLen(0)
       return false
-    let m = c_memchr(addr line.string[pos], '\l'.ord, space)
+    let m = c_memchr(addr line.string[pos], '\l'.ord, sp)
     if m != nil:
       # \l found: Could be our own or the one by fgets, in any case, we're done
       var last = cast[ByteAddress](m) - cast[ByteAddress](addr line.string[0])
@@ -129,17 +130,17 @@ proc readLine(f: File, line: var TaintedString): bool =
         # \0\l\0 => line ending in a null character.
         # \0\l\l => last line without newline, null was put there by fgets.
       elif last > 0 and line.string[last-1] == '\0':
-        if last < pos + space - 1 and line.string[last+1] != '\0':
+        if last < pos + sp - 1 and line.string[last+1] != '\0':
           dec last
       line.string.setLen(last)
       return true
     else:
       # fgets will have inserted a null byte at the end of the string.
-      dec space
+      dec sp
     # No \l found: Increase buffer and read more
-    inc pos, space
-    space = 128 # read in 128 bytes at a time
-    line.string.setLen(pos+space)
+    inc pos, sp
+    sp = 128 # read in 128 bytes at a time
+    line.string.setLen(pos+sp)
 
 proc readLine(f: File): TaintedString =
   result = TaintedString(newStringOfCap(80))
diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim
index 3a93221e0..11034006a 100644
--- a/lib/system/sysstr.nim
+++ b/lib/system/sysstr.nim
@@ -263,27 +263,32 @@ proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {.
   result.len = newLen
 
 # --------------- other string routines ----------------------------------
-proc nimIntToStr(x: int): string {.compilerRtl.} =
-  result = newString(sizeof(x)*4)
+proc add*(result: var string; x: int64) =
+  let base = result.len
+  setLen(result, base + sizeof(x)*4)
   var i = 0
   var y = x
   while true:
     var d = y div 10
-    result[i] = chr(abs(int(y - d*10)) + ord('0'))
+    result[base+i] = chr(abs(int(y - d*10)) + ord('0'))
     inc(i)
     y = d
     if y == 0: break
   if x < 0:
-    result[i] = '-'
+    result[base+i] = '-'
     inc(i)
-  setLen(result, i)
+  setLen(result, base+i)
   # mirror the string:
   for j in 0..i div 2 - 1:
-    swap(result[j], result[i-j-1])
+    swap(result[base+j], result[base+i-j-1])
 
-proc nimFloatToStr(f: float): string {.compilerproc.} =
+proc nimIntToStr(x: int): string {.compilerRtl.} =
+  result = newStringOfCap(sizeof(x)*4)
+  result.add x
+
+proc add*(result: var string; x: float) =
   var buf: array[0..64, char]
-  var n: int = c_sprintf(buf, "%.16g", f)
+  var n: int = c_sprintf(buf, "%.16g", x)
   var hasDot = false
   for i in 0..n-1:
     if buf[i] == ',':
@@ -298,14 +303,18 @@ proc nimFloatToStr(f: float): string {.compilerproc.} =
   # On Windows nice numbers like '1.#INF', '-1.#INF' or '1.#NAN' are produced.
   # We want to get rid of these here:
   if buf[n-1] in {'n', 'N'}:
-    result = "nan"
+    result.add "nan"
   elif buf[n-1] == 'F':
     if buf[0] == '-':
-      result = "-inf"
+      result.add "-inf"
     else:
-      result = "inf"
+      result.add "inf"
   else:
-    result = $buf
+    result.add buf
+
+proc nimFloatToStr(f: float): string {.compilerproc.} =
+  result = newStringOfCap(8)
+  result.add f
 
 proc c_strtod(buf: cstring, endptr: ptr cstring): float64 {.
   importc: "strtod", header: "<stdlib.h>", noSideEffect.}
@@ -469,22 +478,8 @@ proc nimParseBiggestFloat(s: string, number: var BiggestFloat,
   number = c_strtod(t, nil)
 
 proc nimInt64ToStr(x: int64): string {.compilerRtl.} =
-  result = newString(sizeof(x)*4)
-  var i = 0
-  var y = x
-  while true:
-    var d = y div 10
-    result[i] = chr(abs(int(y - d*10)) + ord('0'))
-    inc(i)
-    y = d
-    if y == 0: break
-  if x < 0:
-    result[i] = '-'
-    inc(i)
-  setLen(result, i)
-  # mirror the string:
-  for j in 0..i div 2 - 1:
-    swap(result[j], result[i-j-1])
+  result = newStringOfCap(sizeof(x)*4)
+  result.add x
 
 proc nimBoolToStr(x: bool): string {.compilerRtl.} =
   return if x: "true" else: "false"
diff --git a/lib/upcoming/asyncdispatch.nim b/lib/upcoming/asyncdispatch.nim
index 29b955c46..68ecbe81e 100644
--- a/lib/upcoming/asyncdispatch.nim
+++ b/lib/upcoming/asyncdispatch.nim
@@ -9,9 +9,9 @@
 
 include "system/inclrtl"
 
-import os, oids, tables, strutils, times, heapqueue
+import os, oids, tables, strutils, times, heapqueue, lists
 
-import nativesockets, net, queues
+import nativesockets, net, deques
 
 export Port, SocketFlag
 
@@ -135,7 +135,7 @@ include "../includes/asyncfutures"
 type
   PDispatcherBase = ref object of RootRef
     timers: HeapQueue[tuple[finishAt: float, fut: Future[void]]]
-    callbacks: Queue[proc ()]
+    callbacks: Deque[proc ()]
 
 proc processTimers(p: PDispatcherBase) {.inline.} =
   while p.timers.len > 0 and epochTime() >= p.timers[0].finishAt:
@@ -143,7 +143,7 @@ proc processTimers(p: PDispatcherBase) {.inline.} =
 
 proc processPendingCallbacks(p: PDispatcherBase) =
   while p.callbacks.len > 0:
-    var cb = p.callbacks.dequeue()
+    var cb = p.callbacks.popFirst()
     cb()
 
 proc adjustedTimeout(p: PDispatcherBase, timeout: int): int {.inline.} =
@@ -729,7 +729,7 @@ when defined(windows) or defined(nimdoc):
     var lpOutputBuf = newString(lpOutputLen)
     var dwBytesReceived: Dword
     let dwReceiveDataLength = 0.Dword # We don't want any data to be read.
-    let dwLocalAddressLength = Dword(sizeof (Sockaddr_in) + 16)
+    let dwLocalAddressLength = Dword(sizeof(Sockaddr_in) + 16)
     let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in) + 16)
 
     template completeAccept() {.dirty.} =
@@ -1095,9 +1095,11 @@ else:
     AsyncFD* = distinct cint
     Callback = proc (fd: AsyncFD): bool {.closure,gcsafe.}
 
+    DoublyLinkedListRef = ref DoublyLinkedList[Callback]
+
     AsyncData = object
-      readCB: Callback
-      writeCB: Callback
+      readCBs: DoublyLinkedListRef
+      writeCBs: DoublyLinkedListRef
 
     AsyncEvent* = distinct SelectEvent
 
@@ -1112,7 +1114,7 @@ else:
     new result
     result.selector = newSelector[AsyncData]()
     result.timers.newHeapQueue()
-    result.callbacks = initQueue[proc ()](64)
+    result.callbacks = initDeque[proc ()](64)
 
   var gDisp{.threadvar.}: PDispatcher ## Global dispatcher
   proc getGlobalDispatcher*(): PDispatcher =
@@ -1121,7 +1123,10 @@ else:
 
   proc register*(fd: AsyncFD) =
     let p = getGlobalDispatcher()
-    var data = AsyncData()
+    var data = AsyncData(
+      readCBs: DoublyLinkedListRef(),
+      writeCBs: DoublyLinkedListRef()
+    )
     p.selector.registerHandle(fd.SocketHandle, {}, data)
 
   proc newAsyncNativeSocket*(domain: cint, sockType: cint,
@@ -1156,8 +1161,9 @@ else:
     let p = getGlobalDispatcher()
     var newEvents = {Event.Read}
     withData(p.selector, fd.SocketHandle, adata) do:
-      adata.readCB = cb
-      if adata.writeCB != nil:
+      adata.readCBs[].append(cb)
+      newEvents.incl(Event.Read)
+      if not isNil(adata.writeCBs.head):
         newEvents.incl(Event.Write)
     do:
       raise newException(ValueError, "File descriptor not registered.")
@@ -1167,8 +1173,9 @@ else:
     let p = getGlobalDispatcher()
     var newEvents = {Event.Write}
     withData(p.selector, fd.SocketHandle, adata) do:
-      adata.writeCB = cb
-      if adata.readCB != nil:
+      adata.writeCBs[].append(cb)
+      newEvents.incl(Event.Write)
+      if not isNil(adata.readCBs.head):
         newEvents.incl(Event.Read)
     do:
       raise newException(ValueError, "File descriptor not registered.")
@@ -1194,32 +1201,33 @@ else:
         var fd = keys[i].fd.SocketHandle
         let events = keys[i].events
 
-        if Event.Read in events:
-          let cb = keys[i].data.readCB
-          doAssert(cb != nil)
-          if cb(fd.AsyncFD):
-            p.selector.withData(fd, adata) do:
-              if adata.readCB == cb:
-                adata.readCB = nil
-
-        if Event.Write in events:
-          let cb = keys[i].data.writeCB
-          doAssert(cb != nil)
-          if cb(fd.AsyncFD):
-            p.selector.withData(fd, adata) do:
-              if adata.writeCB == cb:
-                adata.writeCB = nil
+        if Event.Read in events or events == {Event.Error}:
+          for node in keys[i].data.readCBs[].nodes():
+            let cb = node.value
+            if cb != nil:
+              if cb(fd.AsyncFD):
+                keys[i].data.readCBs[].remove(node)
+              else:
+                break
+
+        if Event.Write in events or events == {Event.Error}:
+          for node in keys[i].data.writeCBs[].nodes():
+            let cb = node.value
+            if cb != nil:
+              if cb(fd.AsyncFD):
+                keys[i].data.writeCBs[].remove(node)
+              else:
+                break
 
         when supportedPlatform:
           if (customSet * events) != {}:
-            let cb = keys[i].data.readCB
-            doAssert(cb != nil)
-            custom = true
-            if cb(fd.AsyncFD):
-              p.selector.withData(fd, adata) do:
-                if adata.readCB == cb:
-                  adata.readCB = nil
-                  p.selector.unregister(fd)
+            for node in keys[i].data.readCBs[].nodes():
+              let cb = node.value
+              doAssert(cb != nil)
+              custom = true
+              if cb(fd.AsyncFD):
+                keys[i].data.readCBs[].remove(node)
+                p.selector.unregister(fd)
 
         # because state `data` can be modified in callback we need to update
         # descriptor events with currently registered callbacks.
@@ -1227,8 +1235,8 @@ else:
           var update = false
           var newEvents: set[Event] = {}
           p.selector.withData(fd, adata) do:
-            if adata.readCB != nil: incl(newEvents, Event.Read)
-            if adata.writeCB != nil: incl(newEvents, Event.Write)
+            if not isNil(adata.readCBs.head): incl(newEvents, Event.Read)
+            if not isNil(adata.writeCBs.head): incl(newEvents, Event.Write)
             update = true
           if update:
             p.selector.updateHandle(fd, newEvents)
@@ -1491,21 +1499,33 @@ else:
       ## ``oneshot`` - if ``true`` only one event will be dispatched,
       ## if ``false`` continuous events every ``timeout`` milliseconds.
       let p = getGlobalDispatcher()
-      var data = AsyncData(readCB: cb)
+      var data = AsyncData(
+        readCBs: DoublyLinkedListRef(),
+        writeCBs: DoublyLinkedListRef()
+      )
+      data.readCBs[].append(cb)
       p.selector.registerTimer(timeout, oneshot, data)
 
     proc addSignal*(signal: int, cb: Callback) =
       ## Start watching signal ``signal``, and when signal appears, call the
       ## callback ``cb``.
       let p = getGlobalDispatcher()
-      var data = AsyncData(readCB: cb)
+      var data = AsyncData(
+        readCBs: DoublyLinkedListRef(),
+        writeCBs: DoublyLinkedListRef()
+      )
+      data.readCBs[].append(cb)
       p.selector.registerSignal(signal, data)
 
     proc addProcess*(pid: int, cb: Callback) =
       ## Start watching for process exit with pid ``pid``, and then call
       ## the callback ``cb``.
       let p = getGlobalDispatcher()
-      var data = AsyncData(readCB: cb)
+      var data = AsyncData(
+        readCBs: DoublyLinkedListRef(),
+        writeCBs: DoublyLinkedListRef()
+      )
+      data.readCBs[].append(cb)
       p.selector.registerProcess(pid, data)
 
   proc newAsyncEvent*(): AsyncEvent =
@@ -1524,7 +1544,11 @@ else:
     ## Start watching for event ``ev``, and call callback ``cb``, when
     ## ev will be set to signaled state.
     let p = getGlobalDispatcher()
-    var data = AsyncData(readCB: cb)
+    var data = AsyncData(
+      readCBs: DoublyLinkedListRef(),
+      writeCBs: DoublyLinkedListRef()
+    )
+    data.readCBs[].append(cb)
     p.selector.registerEvent(SelectEvent(ev), data)
 
 proc sleepAsync*(ms: int): Future[void] =
@@ -1591,7 +1615,7 @@ proc recvLine*(socket: AsyncFD): Future[string] {.async.} =
   ## **Note**: This procedure is mostly used for testing. You likely want to
   ## use ``asyncnet.recvLine`` instead.
 
-  template addNLIfEmpty(): stmt =
+  template addNLIfEmpty(): typed =
     if result.len == 0:
       result.add("\c\L")
 
@@ -1614,7 +1638,7 @@ proc recvLine*(socket: AsyncFD): Future[string] {.async.} =
 proc callSoon*(cbproc: proc ()) =
   ## Schedule `cbproc` to be called as soon as possible.
   ## The callback is called when control returns to the event loop.
-  getGlobalDispatcher().callbacks.enqueue(cbproc)
+  getGlobalDispatcher().callbacks.addLast(cbproc)
 
 proc runForever*() =
   ## Begins a never ending global dispatcher poll loop.
diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim
index 9dad7e489..241ad17ae 100644
--- a/lib/wrappers/openssl.nim
+++ b/lib/wrappers/openssl.nim
@@ -26,7 +26,7 @@ when useWinVersion:
   from winlean import SocketHandle
 else:
   const
-    versions = "(|.10|.1.0.1|.1.0.0|.0.9.9|.0.9.8)"
+    versions = "(|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8)"
   when defined(macosx):
     const
       DLLSSLName = "libssl" & versions & ".dylib"
@@ -261,11 +261,14 @@ proc ERR_error_string*(e: cInt, buf: cstring): cstring{.cdecl,
 proc ERR_get_error*(): cInt{.cdecl, dynlib: DLLUtilName, importc.}
 proc ERR_peek_last_error*(): cInt{.cdecl, dynlib: DLLUtilName, importc.}
 
-proc OpenSSL_add_all_algorithms*(){.cdecl, dynlib: DLLUtilName, importc: "OPENSSL_add_all_algorithms_conf".}
+when defined(android):
+    template OpenSSL_add_all_algorithms*() = discard
+else:
+    proc OpenSSL_add_all_algorithms*(){.cdecl, dynlib: DLLUtilName, importc: "OPENSSL_add_all_algorithms_conf".}
 
 proc OPENSSL_config*(configName: cstring){.cdecl, dynlib: DLLSSLName, importc.}
 
-when not useWinVersion and not defined(macosx):
+when not useWinVersion and not defined(macosx) and not defined(android):
   proc CRYPTO_set_mem_functions(a,b,c: pointer){.cdecl,
     dynlib: DLLUtilName, importc.}
 
@@ -279,7 +282,7 @@ when not useWinVersion and not defined(macosx):
     if p != nil: dealloc(p)
 
 proc CRYPTO_malloc_init*() =
-  when not useWinVersion and not defined(macosx):
+  when not useWinVersion and not defined(macosx) and not defined(android):
     CRYPTO_set_mem_functions(allocWrapper, reallocWrapper, deallocWrapper)
 
 proc SSL_CTX_ctrl*(ctx: SslCtx, cmd: cInt, larg: int, parg: pointer): int{.
diff --git a/lib/wrappers/sqlite3.nim b/lib/wrappers/sqlite3.nim
index e7fd2bc36..d2b70df8d 100644
--- a/lib/wrappers/sqlite3.nim
+++ b/lib/wrappers/sqlite3.nim
@@ -96,10 +96,6 @@ const
                               #define SQLITE_TRANSIENT   ((void(*)(void *))-1)
   SQLITE_DETERMINISTIC* = 0x800
 
-const
-  SQLITE_STATIC* = nil
-  SQLITE_TRANSIENT* = cast[pointer](- 1)
-
 type
   Sqlite3 {.pure, final.} = object
   PSqlite3* = ptr Sqlite3
@@ -114,7 +110,7 @@ type
 
   Callback* = proc (para1: pointer, para2: int32, para3,
                      para4: cstringArray): int32{.cdecl.}
-  Tbind_destructor_func* = proc (para1: pointer){.cdecl.}
+  Tbind_destructor_func* = proc (para1: pointer){.cdecl, locks: 0, tags: [].}
   Create_function_step_func* = proc (para1: Pcontext, para2: int32,
                                       para3: PValueArg){.cdecl.}
   Create_function_func_func* = proc (para1: Pcontext, para2: int32,
@@ -132,6 +128,10 @@ type
     Tresult_func: Result_func, Tcreate_collation_func: Create_collation_func,
     Tcollation_needed_func: Collation_needed_func].}
 
+const
+  SQLITE_STATIC* = nil
+  SQLITE_TRANSIENT* = cast[Tbind_destructor_func](-1)
+
 proc close*(para1: PSqlite3): int32{.cdecl, dynlib: Lib, importc: "sqlite3_close".}
 proc exec*(para1: PSqlite3, sql: cstring, para3: Callback, para4: pointer,
            errmsg: var cstring): int32{.cdecl, dynlib: Lib,
@@ -234,7 +234,8 @@ proc bind_parameter_name*(para1: Pstmt, para2: int32): cstring{.cdecl,
     dynlib: Lib, importc: "sqlite3_bind_parameter_name".}
 proc bind_parameter_index*(para1: Pstmt, zName: cstring): int32{.cdecl,
     dynlib: Lib, importc: "sqlite3_bind_parameter_index".}
-  #function sqlite3_clear_bindings(_para1:Psqlite3_stmt):longint;cdecl; external Sqlite3Lib name 'sqlite3_clear_bindings';
+proc clear_bindings*(para1: Pstmt): int32 {.cdecl,
+    dynlib: Lib, importc: "sqlite3_clear_bindings".}
 proc column_count*(pStmt: Pstmt): int32{.cdecl, dynlib: Lib,
     importc: "sqlite3_column_count".}
 proc column_name*(para1: Pstmt, para2: int32): cstring{.cdecl, dynlib: Lib,
diff --git a/readme.md b/readme.md
index 36d1ab0f9..b4a484f60 100644
--- a/readme.md
+++ b/readme.md
@@ -27,7 +27,8 @@ To build from source you will need:
     are: clang, Visual C++, Intel's C++ compiler
   * git or wget
 
-**Note:** When installing ``gcc`` on Ubuntu (and likely other distros) ensure that the ``build-essentials`` package is installed also.
+**Note:** When installing ``gcc`` on Ubuntu (and likely other distros) ensure
+that the ``build-essentials`` package is installed also.
 
 If you are on a fairly modern *nix system, the following steps should work:
 
@@ -58,7 +59,7 @@ source based installations where you added Nim's ``bin`` directory to your PATH
 the easiest way of installing Nimble is via:
 
 ```
-$ nim e install_nimble.nims
+$ koch nimble
 ```
 
 ## Community
diff --git a/tests/assert/tfailedassert.nim b/tests/assert/tfailedassert.nim
index 1e6764471..f0ca149f8 100644
--- a/tests/assert/tfailedassert.nim
+++ b/tests/assert/tfailedassert.nim
@@ -3,7 +3,7 @@ discard """
 WARNING: false first assertion from bar
 ERROR: false second assertion from bar
 -1
-tests/assert/tfailedassert.nim:27 false assertion from foo
+tfailedassert.nim:27 false assertion from foo
 '''
 """
 
diff --git a/tests/async/tasyncall.nim b/tests/async/tasyncall.nim
index 60ba557cc..63b2945a6 100644
--- a/tests/async/tasyncall.nim
+++ b/tests/async/tasyncall.nim
@@ -66,3 +66,12 @@ block:
 
   doAssert execTime * 100 < taskCount * sleepDuration
   doAssert results == expected
+
+block:
+  let
+    noIntFuturesFut = all(newSeq[Future[int]]())
+    noVoidFuturesFut = all(newSeq[Future[void]]())
+
+  doAssert noIntFuturesFut.finished and not noIntFuturesFut.failed
+  doAssert noVoidFuturesFut.finished and not noVoidFuturesFut.failed
+  doAssert noIntFuturesFut.read() == @[]
diff --git a/tests/async/tasynceverror.nim b/tests/async/tasynceverror.nim
deleted file mode 100644
index dd05c831b..000000000
--- a/tests/async/tasynceverror.nim
+++ /dev/null
@@ -1,66 +0,0 @@
-discard """
-  file: "tasynceverror.nim"
-  exitcode: 1
-  outputsub: "Error: unhandled exception: "
-"""
-# error message is actually different on OSX
-import
-    asyncdispatch,
-    asyncnet,
-    nativesockets,
-    os
-
-
-const
-    testHost = "127.0.0.1"
-    testPort = Port(17357)
-
-
-when defined(windows) or defined(nimdoc):
-    # TODO: just make it work on Windows for now.
-    quit("Error: unhandled exception: Connection reset by peer")
-else:
-    proc createListenSocket(host: string, port: Port): TAsyncFD =
-        result = newAsyncNativeSocket()
-
-        SocketHandle(result).setSockOptInt(SOL_SOCKET, SO_REUSEADDR, 1)
-
-        var aiList = getAddrInfo(host, port, AF_INET)
-        if SocketHandle(result).bindAddr(aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32:
-          dealloc(aiList)
-          raiseOSError(osLastError())
-        dealloc(aiList)
-
-        if SocketHandle(result).listen(1) < 0'i32:
-            raiseOSError(osLastError())
-
-
-    proc testAsyncSend() {.async.} =
-        var
-            ls = createListenSocket(testHost, testPort)
-            s = newAsyncSocket()
-
-        await s.connect(testHost, testPort)
-
-        var ps = await ls.accept()
-        closeSocket(ls)
-
-        await ps.send("test 1", flags={})
-        s.close()
-        # This send should raise EPIPE
-        await ps.send("test 2", flags={})
-        SocketHandle(ps).close()
-
-
-    # The bug was, when the poll function handled EvError for us,
-    # our callbacks may never get executed, thus making the event
-    # loop block indefinitely. This is a timer to keep everything
-    # rolling. 400 ms is an arbitrary value, should be enough though.
-    proc timer() {.async.} =
-        await sleepAsync(400)
-        echo("Timer expired.")
-        quit(2)
-
-
-    asyncCheck(testAsyncSend())
-    waitFor(timer())
diff --git a/tests/async/tgenericasync.nim b/tests/async/tgenericasync.nim
new file mode 100644
index 000000000..ab704238a
--- /dev/null
+++ b/tests/async/tgenericasync.nim
@@ -0,0 +1,14 @@
+discard """
+  output: '''123
+abc'''
+"""
+
+# bug #4856
+
+import asyncdispatch
+
+proc say[T](t: T): Future[void] {.async.} =
+  echo $t
+
+waitFor(say(123))
+waitFor(say("abc"))
diff --git a/tests/clearmsg/tmacroerrorproc.nim b/tests/clearmsg/tmacroerrorproc.nim
new file mode 100644
index 000000000..9a6ff6a06
--- /dev/null
+++ b/tests/clearmsg/tmacroerrorproc.nim
@@ -0,0 +1,13 @@
+discard """
+  file: "tmacroerrorproc.nim"
+  line: 13
+  errormsg: "Expected a node of kind nnkCharLit, got nnkCommand"
+"""
+# issue #4915
+import macros
+
+macro mixer(n: typed): untyped =
+  expectKind(n, nnkCharLit)
+  
+mixer:
+  echo "owh"
\ No newline at end of file
diff --git a/tests/collections/ttables.nim b/tests/collections/ttables.nim
index 59fef4920..ef5ed92f5 100644
--- a/tests/collections/ttables.nim
+++ b/tests/collections/ttables.nim
@@ -95,9 +95,24 @@ block orderedTableTest1:
   for key, val in mpairs(t): val = 99
   for val in mvalues(t): assert val == 99
 
+block orderedTableTest2:
+  var
+    s = initOrderedTable[string, int]()
+    t = initOrderedTable[string, int]()
+  assert s == t
+  for key, val in items(data): t[key] = val
+  assert s != t
+  for key, val in items(sorteddata): s[key] = val
+  assert s != t
+  t.clear()
+  assert s != t
+  for key, val in items(sorteddata): t[key] = val
+  assert s == t
+
 block countTableTest1:
   var s = data.toTable
   var t = initCountTable[string]()
+
   for k in s.keys: t.inc(k)
   for k in t.keys: assert t[k] == 1
   t.inc("90", 3)
@@ -115,6 +130,24 @@ block countTableTest1:
     else: break
     inc i
 
+block countTableTest2:
+  var
+    s = initCountTable[int]()
+    t = initCountTable[int]()
+  assert s == t
+  s.inc(1)
+  assert s != t
+  t.inc(2)
+  assert s != t
+  t.inc(1)
+  assert s != t
+  s.inc(2)
+  assert s == t
+  s.inc(1)
+  assert s != t
+  t.inc(1)
+  assert s == t
+
 block mpairsTableTest1:
   var t = initTable[string, int]()
   t["a"] = 1
@@ -134,6 +167,29 @@ block mpairsTableTest1:
 block SyntaxTest:
   var x = toTable[int, string]({:})
 
+block zeroHashKeysTest:
+  proc doZeroHashValueTest[T, K, V](t: T, nullHashKey: K, value: V) =
+    let initialLen = t.len
+    var testTable = t
+    testTable[nullHashKey] = value
+    assert testTable[nullHashKey] == value
+    assert testTable.len == initialLen + 1
+    testTable.del(nullHashKey)
+    assert testTable.len == initialLen
+
+  # with empty table
+  doZeroHashValueTest(toTable[int,int]({:}), 0, 42)
+  doZeroHashValueTest(toTable[string,int]({:}), "", 23)
+  doZeroHashValueTest(toOrderedTable[int,int]({:}), 0, 42)
+  doZeroHashValueTest(toOrderedTable[string,int]({:}), "", 23)
+
+  # with non-empty table
+  doZeroHashValueTest(toTable[int,int]({1:2}), 0, 42)
+  doZeroHashValueTest(toTable[string,string]({"foo": "bar"}), "", "zero")
+  doZeroHashValueTest(toOrderedTable[int,int]({3:4}), 0, 42)
+  doZeroHashValueTest(toOrderedTable[string,string]({"egg": "sausage"}),
+      "", "spam")
+
 # Until #4448 is fixed, these tests will fail
 when false:
   block clearTableTest:
diff --git a/tests/generics/tstatictalias.nim b/tests/generics/tstatictalias.nim
new file mode 100644
index 000000000..98751b8cb
--- /dev/null
+++ b/tests/generics/tstatictalias.nim
@@ -0,0 +1,20 @@
+discard """
+  output: '''G:0,1:0.1
+G:0,1:0.1
+H:1:0.1'''
+"""
+
+type
+  G[i,j:static[int]] = object
+    v:float
+  H[j:static[int]] = G[0,j]
+proc p[i,j:static[int]](x:G[i,j]) = echo "G:",i,",",j,":",x.v
+proc q[j:static[int]](x:H[j]) = echo "H:",j,":",x.v
+
+var
+  g0 = G[0,1](v: 0.1)
+  h0:H[1] = g0
+p(g0)
+p(h0)
+q(h0)
+# bug #4863
diff --git a/tests/js/tconsole.nim b/tests/js/tconsole.nim
new file mode 100644
index 000000000..f6da71c20
--- /dev/null
+++ b/tests/js/tconsole.nim
@@ -0,0 +1,13 @@
+discard """
+  output: '''Hello, console
+1 2 3
+1 'hi' 1.1'''
+"""
+
+# This file tests the JavaScript console
+
+import jsconsole
+
+console.log("Hello, console")
+console.log(1, 2, 3)
+console.log(1, "hi", 1.1)
\ No newline at end of file
diff --git a/tests/macros/tdump.nim b/tests/macros/tdump.nim
new file mode 100644
index 000000000..e4c14dc6b
--- /dev/null
+++ b/tests/macros/tdump.nim
@@ -0,0 +1,13 @@
+discard """
+  output: '''x = 10
+x + y = 30
+'''
+"""
+
+import future
+
+let
+  x = 10
+  y = 20
+dump x
+dump(x + y)
\ No newline at end of file
diff --git a/tests/manyloc/keineschweine/keineschweine.nim b/tests/manyloc/keineschweine/keineschweine.nim
index 49c0a2476..804a22852 100644
--- a/tests/manyloc/keineschweine/keineschweine.nim
+++ b/tests/manyloc/keineschweine/keineschweine.nim
@@ -40,7 +40,7 @@ type
     trailDelay*: float
     body: chipmunk.PBody
     shape: chipmunk.PShape
-import vehicles
+include vehicles
 const
   LGrabbable*  = (1 shl 0).TLayers
   LBorders*    = (1 shl 1).TLayers
diff --git a/tests/manyloc/keineschweine/lib/vehicles.nim b/tests/manyloc/keineschweine/lib/vehicles.nim
index ddfb43b38..e245c9e8c 100644
--- a/tests/manyloc/keineschweine/lib/vehicles.nim
+++ b/tests/manyloc/keineschweine/lib/vehicles.nim
@@ -1,6 +1,6 @@
 import
   sfml, chipmunk,
-  sg_assets, sfml_stuff, "../keineschweine"
+  sg_assets, sfml_stuff#, "../keineschweine"
 
 
 proc accel*(obj: PVehicle, dt: float) =
diff --git a/tests/method/tmapper.nim b/tests/method/tmapper.nim
index 75b36e69a..0008d9033 100644
--- a/tests/method/tmapper.nim
+++ b/tests/method/tmapper.nim
@@ -1,5 +1,5 @@
 discard """
-  errormsg: "invalid declaration order; cannot attach 'step' to method defined here: tests/method/tmapper.nim(22,7)"
+  errormsg: "invalid declaration order; cannot attach 'step' to method defined here: tmapper.nim(22,7)"
   line: 25
 """
 
diff --git a/tests/misc/tvarious1.nim b/tests/misc/tvarious1.nim
index 1d5ad876a..595c77919 100644
--- a/tests/misc/tvarious1.nim
+++ b/tests/misc/tvarious1.nim
@@ -18,15 +18,15 @@ echo v[2]
 
 # bug #569
 
-import queues
+import deques
 
 type
   TWidget = object
-    names: Queue[string]
+    names: Deque[string]
 
-var w = TWidget(names: initQueue[string]())
+var w = TWidget(names: initDeque[string]())
 
-add(w.names, "Whopie")
+addLast(w.names, "Whopie")
 
 for n in w.names: echo(n)
 
diff --git a/tests/modules/trecinca.nim b/tests/modules/trecinca.nim
index 14a91ba5c..7a74d7a46 100644
--- a/tests/modules/trecinca.nim
+++ b/tests/modules/trecinca.nim
@@ -1,7 +1,7 @@
 discard """
-  file: "tests/reject/trecincb.nim"
+  file: "trecincb.nim"
   line: 9
-  errormsg: "recursive dependency: 'tests/modules/trecincb.nim'"
+  errormsg: "recursive dependency: 'trecincb.nim'"
 """
 # Test recursive includes
 
diff --git a/tests/modules/trecincb.nim b/tests/modules/trecincb.nim
index 299a242e1..1d3eb5503 100644
--- a/tests/modules/trecincb.nim
+++ b/tests/modules/trecincb.nim
@@ -1,7 +1,7 @@
 discard """
   file: "trecincb.nim"
   line: 9
-  errormsg: "recursive dependency: 'tests/modules/trecincb.nim'"
+  errormsg: "recursive dependency: 'trecincb.nim'"
 """
 # Test recursive includes
 
diff --git a/tests/modules/trecmod.nim b/tests/modules/trecmod.nim
index d567e293b..5f053bcae 100644
--- a/tests/modules/trecmod.nim
+++ b/tests/modules/trecmod.nim
@@ -1,2 +1,8 @@
+discard """
+  file: "mrecmod.nim"
+  line: 1
+  errormsg: "recursive module dependency detected"
+  disabled: true
+"""
 # recursive module
 import mrecmod
diff --git a/tests/modules/trecmod2.nim b/tests/modules/trecmod2.nim
index 85fe2215f..03c8cf70d 100644
--- a/tests/modules/trecmod2.nim
+++ b/tests/modules/trecmod2.nim
@@ -1,10 +1,13 @@
+discard """
+  output: "4"
+"""
 type
   T1* = int  # Module A exports the type ``T1``
 
 import mrecmod2   # the compiler starts parsing B
-
+# the manual says this should work
 proc main() =
-  var i = p(3) # works because B has been parsed completely here
+  echo p(3) # works because B has been parsed completely here
 
 main()
 
diff --git a/tests/osproc/tworkingdir.nim b/tests/osproc/tworkingdir.nim
new file mode 100644
index 000000000..84ba3375c
--- /dev/null
+++ b/tests/osproc/tworkingdir.nim
@@ -0,0 +1,16 @@
+discard """
+  file: "tworkingdir.nim"
+  output: ""
+"""
+
+import osproc, os
+when defined(windows):
+  # Windows don't have this issue, so we won't test it.
+  discard
+else:
+  let dir1 = getCurrentDir()
+  var process = startProcess("/usr/bin/env", "/usr/bin", ["true"])
+  let dir2 = getCurrentDir()
+  discard process.waitForExit()
+  process.close()
+  doAssert(dir1 == dir2, $dir1 & " != " & $dir2)
diff --git a/tests/parser/tbraces.nim b/tests/parser/tbraces.nim
new file mode 100644
index 000000000..86c854546
--- /dev/null
+++ b/tests/parser/tbraces.nim
@@ -0,0 +1,432 @@
+#? braces
+
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2015 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module implements some common generic algorithms.
+
+type
+  SortOrder* = enum {  ## sort order
+    Descending, Ascending
+  }
+
+
+type(
+  DummyAlias = int
+  OtherAlias = distinct char
+
+  SomeObject = object of RootObj { ## declaration here
+    fieldA, fieldB: int
+    case order: SortOrder {
+      of Descending {a: string}
+      of Ascending {b: seq[char]}
+    }
+  }
+
+  MyConcept = concept x {
+     x is int
+  }
+)
+
+{.deprecated: [TSortOrder: SortOrder].}
+
+
+proc `*`*(x: int, order: SortOrder): int @inline {
+  ## flips `x` if ``order == Descending``;
+  ## if ``order == Ascending`` then `x` is returned.
+  ## `x` is supposed to be the result of a comparator, ie ``< 0`` for
+  ## *less than*, ``== 0`` for *equal*, ``> 0`` for *greater than*.
+  var y = order.ord - 1
+  result = (x xor y) - y
+}
+
+proc fill*[T](a: var openArray[T], first, last: Natural, value: T) {
+  ## fills the array ``a[first..last]`` with `value`.
+  var x = first
+  while x <= last {
+    a[x] = value
+    inc(x)
+  }
+}
+
+proc fill*[T](a: var openArray[T], value: T) {
+  ## fills the array `a` with `value`.
+  fill(a, 0, a.high, value)
+}
+
+proc reverse*[T](a: var openArray[T], first, last: Natural) {
+  ## reverses the array ``a[first..last]``.
+  var x = first
+  var y = last
+  while x < y {
+    swap(a[x], a[y])
+    dec(y)
+    inc(x)
+  }
+}
+
+proc reverse*[T](a: var openArray[T]) {
+  ## reverses the array `a`.
+  reverse(a, 0, a.high)
+}
+
+proc reversed*[T](a: openArray[T], first: Natural, last: int): seq[T] {
+  ## returns the reverse of the array `a[first..last]`.
+  assert last >= first-1
+  var i = last - first
+  var x = first.int
+  result = newSeq[T](i + 1)
+  while i >= 0 {
+    result[i] = a[x]
+    dec(i)
+    inc(x)
+  }
+}
+
+proc reversed*[T](a: openArray[T]): seq[T] {
+  ## returns the reverse of the array `a`.
+  reversed(a, 0, a.high)
+}
+
+proc binarySearch*[T](a: openArray[T], key: T): int {
+  ## binary search for `key` in `a`. Returns -1 if not found.
+  var b = len(a)
+  while result < b {
+    var mid = (result + b) div 2
+    if a[mid] < key { result = mid + 1 } else { b = mid }
+  }
+  if result >= len(a) or a[result] != key { result = -1 }
+}
+
+proc smartBinarySearch*[T](a: openArray[T], key: T): int {
+  ## ``a.len`` must be a power of 2 for this to work.
+  var step = a.len div 2
+  while step > 0 {
+    if a[result or step] <= key { result = result or step }
+    step = step shr 1
+  }
+  if a[result] != key { result = -1 }
+}
+
+const (
+  onlySafeCode = true
+)
+
+proc lowerBound*[T](a: openArray[T], key: T, cmp: proc(x,y: T): int @closure): int {
+  ## same as binarySearch except that if key is not in `a` then this
+  ## returns the location where `key` would be if it were. In other
+  ## words if you have a sorted sequence and you call
+  ## insert(thing, elm, lowerBound(thing, elm))
+  ## the sequence will still be sorted.
+  ##
+  ## `cmp` is the comparator function to use, the expected return values are
+  ## the same as that of system.cmp.
+  ##
+  ## example::
+  ##
+  ##   var arr = @[1,2,3,5,6,7,8,9]
+  ##   arr.insert(4, arr.lowerBound(4))
+  ##   # after running the above arr is `[1,2,3,4,5,6,7,8,9]`
+  result = a.low
+  var count = a.high - a.low + 1
+  var step, pos: int
+  while count != 0 {
+    step = count div 2
+    pos = result + step
+    if cmp(a[pos], key) < 0 {
+      result = pos + 1
+      count -= step + 1
+    } else {
+      count = step
+    }
+  }
+}
+
+proc lowerBound*[T](a: openArray[T], key: T): int { lowerBound(a, key, cmp[T]) }
+proc merge[T](a, b: var openArray[T], lo, m, hi: int,
+              cmp: proc (x, y: T): int @closure, order: SortOrder) {
+  template `<-` (a, b) {
+    when false {
+      a = b
+    } elif onlySafeCode {
+      shallowCopy(a, b)
+    } else {
+      copyMem(addr(a), addr(b), sizeof(T))
+    }
+  }
+  # optimization: If max(left) <= min(right) there is nothing to do!
+  # 1 2 3 4  ## 5 6 7 8
+  # -> O(n) for sorted arrays.
+  # On random data this safes up to 40% of merge calls
+  if cmp(a[m], a[m+1]) * order <= 0 { return }
+  var j = lo
+  # copy a[j..m] into b:
+  assert j <= m
+  when onlySafeCode {
+    var bb = 0
+    while j <= m {
+      b[bb] <- a[j]
+      inc(bb)
+      inc(j)
+    }
+  } else {
+    copyMem(addr(b[0]), addr(a[j]), sizeof(T)*(m-j+1))
+    j = m+1
+  }
+  var i = 0
+  var k = lo
+  # copy proper element back:
+  while k < j and j <= hi {
+    if cmp(b[i], a[j]) * order <= 0 {
+      a[k] <- b[i]
+      inc(i)
+    } else {
+      a[k] <- a[j]
+      inc(j)
+    }
+    inc(k)
+  }
+  # copy rest of b:
+  when onlySafeCode {
+    while k < j {
+      a[k] <- b[i]
+      inc(k)
+      inc(i)
+    }
+  } else {
+    if k < j { copyMem(addr(a[k]), addr(b[i]), sizeof(T)*(j-k)) }
+  }
+}
+
+proc sort*[T](a: var openArray[T],
+              cmp: proc (x, y: T): int @closure,
+              order = SortOrder.Ascending) {
+  ## Default Nim sort (an implementation of merge sort). The sorting
+  ## is guaranteed to be stable and the worst case is guaranteed to
+  ## be O(n log n).
+  ## The current implementation uses an iterative
+  ## mergesort to achieve this. It uses a temporary sequence of
+  ## length ``a.len div 2``. Currently Nim does not support a
+  ## sensible default argument for ``cmp``, so you have to provide one
+  ## of your own. However, the ``system.cmp`` procs can be used:
+  ##
+  ## .. code-block:: nim
+  ##
+  ##    sort(myIntArray, system.cmp[int])
+  ##
+  ##    # do not use cmp[string] here as we want to use the specialized
+  ##    # overload:
+  ##    sort(myStrArray, system.cmp)
+  ##
+  ## You can inline adhoc comparison procs with the `do notation
+  ## <manual.html#procedures-do-notation>`_. Example:
+  ##
+  ## .. code-block:: nim
+  ##
+  ##   people.sort do (x, y: Person) -> int:
+  ##     result = cmp(x.surname, y.surname)
+  ##     if result == 0:
+  ##       result = cmp(x.name, y.name)
+  var n = a.len
+  var b: seq[T]
+  newSeq(b, n div 2)
+  var s = 1
+  while s < n {
+    var m = n-1-s
+    while m >= 0 {
+      merge(a, b, max(m-s+1, 0), m, m+s, cmp, order)
+      dec(m, s*2)
+    }
+    s = s*2
+  }
+}
+
+proc sorted*[T](a: openArray[T], cmp: proc(x, y: T): int {.closure.},
+                order = SortOrder.Ascending): seq[T] {
+  ## returns `a` sorted by `cmp` in the specified `order`.
+  result = newSeq[T](a.len)
+  for i in 0 .. a.high { result[i] = a[i] }
+  sort(result, cmp, order)
+}
+
+template sortedByIt*(seq1, op: untyped): untyped {
+  ## Convenience template around the ``sorted`` proc to reduce typing.
+  ##
+  ## The template injects the ``it`` variable which you can use directly in an
+  ## expression. Example:
+  ##
+  ## .. code-block:: nim
+  ##
+  ##   type Person = tuple[name: string, age: int]
+  ##   var
+  ##     p1: Person = (name: "p1", age: 60)
+  ##     p2: Person = (name: "p2", age: 20)
+  ##     p3: Person = (name: "p3", age: 30)
+  ##     p4: Person = (name: "p4", age: 30)
+  ##     people = @[p1,p2,p4,p3]
+  ##
+  ##   echo people.sortedByIt(it.name)
+  ##
+  ## Because the underlying ``cmp()`` is defined for tuples you can do
+  ## a nested sort like in the following example:
+  ##
+  ## .. code-block:: nim
+  ##
+  ##   echo people.sortedByIt((it.age, it.name))
+  ##
+  var result = sorted(seq1, proc(x, y: type[seq1[0]]): int {
+    var it {.inject.} = x
+    let a = op
+    it = y
+    let b = op
+    result = cmp(a, b)
+  })
+  result
+}
+
+proc isSorted*[T](a: openarray[T],
+                 cmp: proc(x, y: T): int {.closure.},
+                 order = SortOrder.Ascending): bool {
+  ## Checks to see whether `a` is already sorted in `order`
+  ## using `cmp` for the comparison. Parameters identical
+  ## to `sort`
+  result = true
+  for i in 0..<len(a)-1 {
+    case cmp(a[i],a[i+1]) * order > 0 {
+      of true { return false }
+      of false {}
+    }
+  }
+}
+
+proc product*[T](x: openArray[seq[T]]): seq[seq[T]] {
+  ## produces the Cartesian product of the array. Warning: complexity
+  ## may explode.
+  result = newSeq[seq[T]]()
+  if x.len == 0 { return }
+  if x.len == 1 {
+    result = @x
+    return
+  }
+  var (
+    indexes = newSeq[int](x.len)
+    initial = newSeq[int](x.len)
+    index = 0
+  )
+  var next = newSeq[T]()
+  next.setLen(x.len)
+  for i in 0..(x.len-1) {
+    if len(x[i]) == 0 { return }
+    initial[i] = len(x[i])-1
+  }
+  indexes = initial
+  while true {
+    while indexes[index] == -1 {
+      indexes[index] = initial[index]
+      index += 1
+      if index == x.len { return }
+      indexes[index] -= 1
+    }
+    for ni, i in indexes {
+      next[ni] = x[ni][i]
+    }
+    var res: seq[T]
+    shallowCopy(res, next)
+    result.add(res)
+    index = 0
+    indexes[index] -= 1
+  }
+}
+
+proc nextPermutation*[T](x: var openarray[T]): bool {.discardable.} {
+  ## Calculates the next lexicographic permutation, directly modifying ``x``.
+  ## The result is whether a permutation happened, otherwise we have reached
+  ## the last-ordered permutation.
+  ##
+  ## .. code-block:: nim
+  ##
+  ##     var v = @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+  ##     v.nextPermutation()
+  ##     echo v # @[0, 1, 2, 3, 4, 5, 6, 7, 9, 8]
+  if x.len < 2 {
+    return false }
+
+  var i = x.high
+  while i > 0 and x[i-1] >= x[i] { dec i }
+  if i == 0 { return false }
+
+  var j = x.high
+  while j >= i and x[j] <= x[i-1] { dec j }
+
+  swap x[j], x[i-1]
+  x.reverse(i, x.high)
+
+  result = true
+}
+
+proc prevPermutation*[T](x: var openarray[T]): bool @discardable {
+  ## Calculates the previous lexicographic permutation, directly modifying
+  ## ``x``.  The result is whether a permutation happened, otherwise we have
+  ## reached the first-ordered permutation.
+  ##
+  ## .. code-block:: nim
+  ##
+  ##     var v = @[0, 1, 2, 3, 4, 5, 6, 7, 9, 8]
+  ##     v.prevPermutation()
+  ##     echo v # @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+  if x.len < 2 { return false }
+
+  var i = x.high
+  while i > 0 and x[i-1] <= x[i] {
+    dec i
+  }
+  if i == 0 { return false }
+
+  x.reverse(i, x.high)
+
+  var j = x.high
+  while j >= i and x[j-1] < x[i-1] {
+    dec j
+  }
+  swap x[i-1], x[j]
+
+  result = true
+}
+
+when isMainModule {
+  # Tests for lowerBound
+  var arr = @[1,2,3,5,6,7,8,9]
+  assert arr.lowerBound(0) == 0
+  assert arr.lowerBound(4) == 3
+  assert arr.lowerBound(5) == 3
+  assert arr.lowerBound(10) == 8
+  arr = @[1,5,10]
+  try {
+    assert arr.lowerBound(4) == 1
+    assert arr.lowerBound(5) == 1
+    assert arr.lowerBound(6) == 2
+  } except ValueError {}
+  # Tests for isSorted
+  var srt1 = [1,2,3,4,4,4,4,5]
+  var srt2 = ["iello","hello"]
+  var srt3 = [1.0,1.0,1.0]
+  var srt4: seq[int] = @[]
+  assert srt1.isSorted(cmp) == true
+  assert srt2.isSorted(cmp) == false
+  assert srt3.isSorted(cmp) == true
+  var srtseq = newSeq[int]()
+  assert srtseq.isSorted(cmp) == true
+  # Tests for reversed
+  var arr1 = @[0,1,2,3,4]
+  assert arr1.reversed() == @[4,3,2,1,0]
+  for i in 0 .. high(arr1) {
+    assert arr1.reversed(0, i) == arr1.reversed()[high(arr1) - i .. high(arr1)]
+    assert arr1.reversed(i, high(arr1)) == arr1.reversed()[0 .. high(arr1) - i]
+  }
+}
diff --git a/tests/stdlib/tgetfileinfo.nim b/tests/stdlib/tgetfileinfo.nim
index 8a0538a5f..1c897b702 100644
--- a/tests/stdlib/tgetfileinfo.nim
+++ b/tests/stdlib/tgetfileinfo.nim
@@ -1,5 +1,5 @@
 discard """
-  output: ""
+  output: "pcDir\npcFile\npcLinkToDir\npcLinkToFile\n"
 """
 
 import os, strutils
@@ -93,4 +93,42 @@ proc testGetFileInfo =
       discard
       #echo("Handle : Invalid File : Success")
 
+  # Test kind for files, directories and symlinks.
+  block:
+    let
+      tmp = getTempDir()
+      dirPath      = tmp / "test-dir"
+      filePath     = tmp / "test-file"
+      linkDirPath  = tmp / "test-link-dir"
+      linkFilePath = tmp / "test-link-file"
+
+    createDir(dirPath)
+    writeFile(filePath, "")
+    when defined(posix):
+      createSymlink(dirPath, linkDirPath)
+      createSymlink(filePath, linkFilePath)
+
+    let
+      dirInfo = getFileInfo(dirPath)
+      fileInfo = getFileInfo(filePath)
+    when defined(posix):
+      let
+        linkDirInfo = getFileInfo(linkDirPath, followSymlink = false)
+        linkFileInfo = getFileInfo(linkFilePath, followSymlink = false)
+
+    echo dirInfo.kind
+    echo fileInfo.kind
+    when defined(posix):
+      echo linkDirInfo.kind
+      echo linkFileInfo.kind
+    else:
+      echo pcLinkToDir
+      echo pcLinkToFile
+
+    removeDir(dirPath)
+    removeFile(filePath)
+    when defined(posix):
+      removeFile(linkDirPath)
+      removeFile(linkFilePath)
+
 testGetFileInfo()
diff --git a/tests/stdlib/tmitems.nim b/tests/stdlib/tmitems.nim
index c713d91a4..17265e1f7 100644
--- a/tests/stdlib/tmitems.nim
+++ b/tests/stdlib/tmitems.nim
@@ -98,13 +98,13 @@ block:
     x += 10
   echo sl
 
-import queues
+import deques
 
 block:
-  var q = initQueue[int]()
-  q.add(1)
-  q.add(2)
-  q.add(3)
+  var q = initDeque[int]()
+  q.addLast(1)
+  q.addLast(2)
+  q.addLast(3)
   for x in q.mitems:
     x += 10
   echo q
diff --git a/tests/stdlib/tnilecho.nim b/tests/stdlib/tnilecho.nim
new file mode 100644
index 000000000..147b5e492
--- /dev/null
+++ b/tests/stdlib/tnilecho.nim
@@ -0,0 +1,2 @@
+var x = @["1", nil, "3"]
+doAssert $x == "@[1, nil, 3]"
diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim
index 1ddaacfcb..1ef02f97e 100644
--- a/tests/stdlib/tos.nim
+++ b/tests/stdlib/tos.nim
@@ -36,6 +36,9 @@ false
 false
 false
 false
+true
+true
+Raises
 '''
 """
 # test os path creation, iteration, and deletion
@@ -86,3 +89,22 @@ for file in files:
 
 removeDir(dname)
 echo dirExists(dname)
+
+# createDir should create recursive directories
+createDir(dirs[0] / dirs[1])
+echo dirExists(dirs[0] / dirs[1]) # true
+removeDir(dirs[0])
+
+# createDir should properly handle trailing separator
+createDir(dname / "")
+echo dirExists(dname) # true
+removeDir(dname)
+
+# createDir should raise IOError if the path exists
+# and is not a directory
+open(dname, fmWrite).close
+try:
+  createDir(dname)
+except IOError:
+  echo "Raises"
+removeFile(dname)
diff --git a/tests/stdlib/ttime.nim b/tests/stdlib/ttime.nim
index 3ab287c4e..b28d8aecd 100644
--- a/tests/stdlib/ttime.nim
+++ b/tests/stdlib/ttime.nim
@@ -9,77 +9,97 @@ import
 # $ date --date='@2147483647'
 # Tue 19 Jan 03:14:07 GMT 2038
 
-var t = getGMTime(fromSeconds(2147483647))
-doAssert t.format("ddd dd MMM hh:mm:ss ZZZ yyyy") == "Tue 19 Jan 03:14:07 UTC 2038"
-doAssert t.format("ddd ddMMMhh:mm:ssZZZyyyy") == "Tue 19Jan03:14:07UTC2038"
-
-doAssert t.format("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 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 UTC"
-
-doAssert t.format("yyyyMMddhhmmss") == "20380119031407"
-
-var t2 = getGMTime(fromSeconds(160070789)) # Mon 27 Jan 16:06:29 GMT 1975
-doAssert t2.format("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 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 UTC"
+proc checkFormat(t: TimeInfo, format, expected: string) =
+  let actual = t.format(format)
+  if actual != expected:
+    echo "Formatting failure!"
+    echo "expected: ", expected
+    echo "actual  : ", actual
+    doAssert false
+
+let t = getGMTime(fromSeconds(2147483647))
+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")
+
+let t2 = getGMTime(fromSeconds(160070789)) # 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")
 
 when not defined(JS):
   when sizeof(Time) == 8:
     var t3 = getGMTime(fromSeconds(889067643645)) # Fri  7 Jun 19:20:45 BST 30143
-    doAssert t3.format("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 ZZZ") ==
-      "7 07 Fri Friday 6 06 18 18 20 20 6 06 Jun June 45 45 P PM 3 43 143 0143 30143 0 00 00:00 UTC"
-    doAssert t3.format(":,[]()-/") == ":,[]()-/"
+    t3.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",
+      "7 07 Fri Friday 6 06 18 18 20 20 6 06 Jun June 45 45 P PM 3 43 143 0143 30143 +0 +00 +00:00")
+    t3.checkFormat(":,[]()-/", ":,[]()-/")
 
 var t4 = getGMTime(fromSeconds(876124714)) # Mon  6 Oct 08:58:34 BST 1997
-doAssert t4.format("M MM MMM MMMM") == "10 10 Oct October"
+t4.checkFormat("M MM MMM MMMM", "10 10 Oct October")
 
 # Interval tests
-doAssert((t4 - initInterval(years = 2)).format("yyyy") == "1995")
-doAssert((t4 - initInterval(years = 7, minutes = 34, seconds = 24)).format("yyyy mm ss") == "1990 24 10")
+(t4 - initInterval(years = 2)).checkFormat("yyyy", "1995")
+(t4 - initInterval(years = 7, minutes = 34, seconds = 24)).checkFormat("yyyy mm ss", "1990 24 10")
 
 proc parseTest(s, f, sExpected: string, ydExpected: int) =
-  let parsed = s.parse(f)
-  doAssert($parsed == sExpected)
+  let
+    parsed = s.parse(f)
+    parsedStr = $getGMTime(toTime(parsed))
+  if parsedStr != sExpected:
+    echo "Parsing failure!"
+    echo "expected: ", sExpected
+    echo "actual  : ", parsedStr
+    doAssert false
   doAssert(parsed.yearday == ydExpected)
 proc parseTestTimeOnly(s, f, sExpected: string) =
   doAssert(sExpected in $s.parse(f))
 
-parseTest("Tuesday at 09:04am on Dec 15, 2015",
-    "dddd at hh:mmtt on MMM d, yyyy", "Tue Dec 15 09:04:00 2015", 348)
+# because setting a specific timezone for testing is platform-specific, we use
+# explicit timezone offsets in all tests.
+
+parseTest("Tuesday at 09:04am on Dec 15, 2015 +0",
+    "dddd at hh:mmtt on MMM d, yyyy z", "2015-12-15T09:04:00+00:00", 348)
 # ANSIC       = "Mon Jan _2 15:04:05 2006"
-parseTest("Thu Jan 12 15:04:05 2006", "ddd MMM dd HH:mm:ss yyyy",
-    "Thu Jan 12 15:04:05 2006", 11)
+parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z",
+    "2006-01-12T15:04:05+00:00", 11)
 # UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
-parseTest("Thu Jan 12 15:04:05 MST 2006", "ddd MMM dd HH:mm:ss ZZZ yyyy",
-    "Thu Jan 12 15:04:05 2006", 11)
+parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z",
+    "2006-01-12T15:04:05+00:00", 11)
 # RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
-parseTest("Mon Feb 29 15:04:05 -07:00 2016", "ddd MMM dd HH:mm:ss zzz yyyy",
-    "Mon Feb 29 15:04:05 2016", 59) # leap day
+parseTest("Mon Feb 29 15:04:05 -07:00 2016 +0", "ddd MMM dd HH:mm:ss zzz yyyy z",
+    "2016-02-29T15:04:05+00:00", 59) # leap day
 # RFC822      = "02 Jan 06 15:04 MST"
-parseTest("12 Jan 16 15:04 MST", "dd MMM yy HH:mm ZZZ",
-    "Tue Jan 12 15:04:00 2016", 11)
+parseTest("12 Jan 16 15:04 +0", "dd MMM yy HH:mm z",
+    "2016-01-12T15:04:00+00:00", 11)
 # RFC822Z     = "02 Jan 06 15:04 -0700" # RFC822 with numeric zone
 parseTest("01 Mar 16 15:04 -07:00", "dd MMM yy HH:mm zzz",
-    "Tue Mar  1 15:04:00 2016", 60) # day after february in leap year
+    "2016-03-01T22:04:00+00:00", 60) # day after february in leap year
 # RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
-parseTest("Monday, 12-Jan-06 15:04:05 MST", "dddd, dd-MMM-yy HH:mm:ss ZZZ",
-    "Thu Jan 12 15:04:05 2006", 11)
+parseTest("Monday, 12-Jan-06 15:04:05 +0", "dddd, dd-MMM-yy HH:mm:ss z",
+    "2006-01-12T15:04:05+00:00", 11)
 # RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
-parseTest("Sun, 01 Mar 2015 15:04:05 MST", "ddd, dd MMM yyyy HH:mm:ss ZZZ",
-    "Sun Mar  1 15:04:05 2015", 59) # day after february in non-leap year
+parseTest("Sun, 01 Mar 2015 15:04:05 +0", "ddd, dd MMM yyyy HH:mm:ss z",
+    "2015-03-01T15:04:05+00:00", 59) # day after february in non-leap year
 # RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" # RFC1123 with numeric zone
 parseTest("Thu, 12 Jan 2006 15:04:05 -07:00", "ddd, dd MMM yyyy HH:mm:ss zzz",
-    "Thu Jan 12 15:04:05 2006", 11)
+    "2006-01-12T22:04:05+00:00", 11)
 # RFC3339     = "2006-01-02T15:04:05Z07:00"
 parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-ddTHH:mm:ssZzzz",
-    "Thu Jan 12 15:04:05 2006", 11)
+    "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",
-    "Thu Jan 12 15:04:05 2006", 11)
+    "2006-01-12T22:04:05+00:00", 11)
 # RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
 parseTest("2006-01-12T15:04:05.999999999Z-07:00",
-    "yyyy-MM-ddTHH:mm:ss.999999999Zzzz", "Thu Jan 12 15:04:05 2006", 11)
+    "yyyy-MM-ddTHH:mm:ss.999999999Zzzz", "2006-01-12T22:04:05+00:00", 11)
+for tzFormat in ["z", "zz", "zzz"]:
+  # formatting timezone as 'Z' for UTC
+  parseTest("2001-01-12T22:04:05Z", "yyyy-MM-dd'T'HH:mm:ss" & tzFormat,
+      "2001-01-12T22:04:05+00:00", 11)
 # Kitchen     = "3:04PM"
 parseTestTimeOnly("3:04PM", "h:mmtt", "15:04:00")
 #when not defined(testing):
@@ -101,21 +121,20 @@ doAssert getDayOfWeekJulian(21, 9, 1970) == dMon
 doAssert getDayOfWeekJulian(1, 1, 2000) == dSat
 doAssert getDayOfWeekJulian(1, 1, 2021) == dFri
 
-# toSeconds tests with GM and Local timezones
-#var t4 = getGMTime(fromSeconds(876124714)) # Mon  6 Oct 08:58:34 BST 1997
-var t4L = getLocalTime(fromSeconds(876124714))
-doAssert toSeconds(timeInfoToTime(t4L)) == 876124714    # fromSeconds is effectively "localTime"
-doAssert toSeconds(timeInfoToTime(t4L)) + t4L.timezone.float == toSeconds(timeInfoToTime(t4))
+# toSeconds tests with GM timezone
+let t4L = getGMTime(fromSeconds(876124714))
+doAssert toSeconds(toTime(t4L)) == 876124714
+doAssert toSeconds(toTime(t4L)) + t4L.timezone.float == toSeconds(toTime(t4))
 
 # adding intervals
 var
-  a1L = toSeconds(timeInfoToTime(t4L + initInterval(hours = 1))) + t4L.timezone.float
-  a1G = toSeconds(timeInfoToTime(t4)) + 60.0 * 60.0
+  a1L = toSeconds(toTime(t4L + initInterval(hours = 1))) + t4L.timezone.float
+  a1G = toSeconds(toTime(t4)) + 60.0 * 60.0
 doAssert a1L == a1G
 
 # subtracting intervals
-a1L = toSeconds(timeInfoToTime(t4L - initInterval(hours = 1))) + t4L.timezone.float
-a1G = toSeconds(timeInfoToTime(t4)) - (60.0 * 60.0)
+a1L = toSeconds(toTime(t4L - initInterval(hours = 1))) + t4L.timezone.float
+a1G = toSeconds(toTime(t4)) - (60.0 * 60.0)
 doAssert a1L == a1G
 
 # add/subtract TimeIntervals and Time/TimeInfo
@@ -140,4 +159,71 @@ doAssert initInterval(months = 13) == initInterval(months = 1, years = 1)
 # Bug with adding a day to a Time
 let day = 24.hours
 let tomorrow = getTime() + day
-doAssert tomorrow - getTime() == 60*60*24
\ No newline at end of file
+doAssert tomorrow - getTime() == 60*60*24
+
+doAssert milliseconds(1000 * 60) == minutes(1)
+doAssert milliseconds(1000 * 60 * 60) == hours(1)
+doAssert milliseconds(1000 * 60 * 60 * 24) == days(1)
+doAssert seconds(60 * 60) == hours(1)
+doAssert seconds(60 * 60 * 24) == days(1)
+doAssert seconds(60 * 60 + 65) == (hours(1) + minutes(1) + seconds(5))
+
+# Bug with parse not setting DST properly if the current local DST differs from
+# the date being parsed. Need to test parse dates both in and out of DST. We
+# are testing that be relying on the fact that tranforming a TimeInfo to a Time
+# and back again will correctly set the DST value. With the incorrect parse
+# behavior this will introduce a one hour offset from the named time and the
+# parsed time if the DST value differs between the current time and the date we
+# are parsing.
+#
+# Unfortunately these tests depend on the locale of the system in which they
+# are run. They will not be meaningful when run in a locale without DST. They
+# also assume that Jan. 1 and Jun. 1 will have differing isDST values.
+let dstT1 = parse("2016-01-01 00:00:00", "yyyy-MM-dd HH:mm:ss")
+let dstT2 = parse("2016-06-01 00:00:00", "yyyy-MM-dd HH:mm:ss")
+doAssert dstT1 == getLocalTime(toTime(dstT1))
+doAssert dstT2 == getLocalTime(toTime(dstT2))
+
+# Comparison between Time objects should be detected by compiler
+# as 'noSideEffect'.
+proc cmpTimeNoSideEffect(t1: Time, t2: Time): bool {.noSideEffect.} =
+  result = t1 == t2
+doAssert cmpTimeNoSideEffect(0.fromSeconds, 0.fromSeconds)
+# Additionally `==` generic for seq[T] has explicit 'noSideEffect' pragma
+# so we can check above condition by comparing seq[Time] sequences
+let seqA: seq[Time] = @[]
+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 = TimeInfo(monthday: 1, timezone: tz[0])
+  doAssert ti.format("z") == tz[1]
+  doAssert ti.format("zz") == tz[2]
+  doAssert ti.format("zzz") == tz[3]
+
+block dstTest:
+  let nonDst = TimeInfo(year: 2015, month: mJan, monthday: 01, yearday: 0,
+      weekday: dThu, hour: 00, minute: 00, second: 00, isDST: false, timezone: 0)
+  var dst = nonDst
+  dst.isDst = true
+  # note that both isDST == true and isDST == false are valid here because
+  # DST is in effect on January 1st in some southern parts of Australia.
+
+  doAssert nonDst.toTime() - dst.toTime() == 3600
+  doAssert nonDst.format("z") == "+0"
+  doAssert dst.format("z") == "+1"
+
+  # parsing will set isDST in relation to the local time. We take a date in
+  # January and one in July to maximize the probability to hit one date with DST
+  # and one without on the local machine. However, this is not guaranteed.
+  let
+    parsedJan = parse("2016-01-05 04:00:00+01:00", "yyyy-MM-dd HH:mm:sszzz")
+    parsedJul = parse("2016-07-01 04:00:00+01:00", "yyyy-MM-dd HH:mm:sszzz")
+  doAssert toTime(parsedJan) == fromSeconds(1451962800)
+  doAssert toTime(parsedJul) == fromSeconds(1467342000)
diff --git a/tests/system/tdeepcopy.nim b/tests/system/tdeepcopy.nim
new file mode 100644
index 000000000..5a582425a
--- /dev/null
+++ b/tests/system/tdeepcopy.nim
@@ -0,0 +1,95 @@
+discard """
+  output: "ok"
+  disabled: "true"
+"""
+
+import tables, lists
+
+type
+  ListTable[K, V] = object
+    valList: DoublyLinkedList[V]
+    table: Table[K, DoublyLinkedNode[V]]
+
+  ListTableRef*[K, V] = ref ListTable[K, V]
+
+proc initListTable*[K, V](initialSize = 64): ListTable[K, V] =
+  result.valList = initDoublyLinkedList[V]()
+  result.table = initTable[K, DoublyLinkedNode[V]]()
+
+proc newListTable*[K, V](initialSize = 64): ListTableRef[K, V] =
+  new(result)
+  result[] = initListTable[K, V](initialSize)
+
+proc `[]=`*[K, V](t: var ListTable[K, V], key: K, val: V) =
+  if key in t.table:
+    t.table[key].value = val
+  else:
+    let node = newDoublyLinkedNode(val)
+    t.valList.append(node)
+    t.table[key] = node
+
+proc `[]`*[K, V](t: ListTable[K, V], key: K): var V {.inline.} =
+  result = t.table[key].value
+
+proc len*[K, V](t: ListTable[K, V]): Natural {.inline.} =
+  result = t.table.len
+
+iterator values*[K, V](t: ListTable[K, V]): V =
+  for val in t.valList.items():
+    yield val
+
+proc `[]=`*[K, V](t: ListTableRef[K, V], key: K, val: V) =
+  t[][key] = val
+
+proc `[]`*[K, V](t: ListTableRef[K, V], key: K): var V {.inline.} =
+  t[][key]
+
+proc len*[K, V](t: ListTableRef[K, V]): Natural {.inline.} =
+  t[].len
+
+iterator values*[K, V](t: ListTableRef[K, V]): V =
+  for val in t[].values:
+    yield val
+
+proc main() =
+  type SomeObj = ref object
+
+  for outer in 0..10_000:
+    let myObj = new(SomeObj)
+    let table = newListTable[int, SomeObj]()
+
+    table[0] = myObj
+    for i in 1..100:
+      table[i] = new(SomeObj)
+
+    var myObj2: SomeObj
+    for val in table.values():
+      if myObj2.isNil:
+        myObj2 = val
+    assert(myObj == myObj2) # passes
+
+    var tableCopy: ListTableRef[int, SomeObj]
+    deepCopy(tableCopy, table)
+
+    let myObjCopy = tableCopy[0]
+    var myObjCopy2: SomeObj = nil
+    for val in tableCopy.values():
+      if myObjCopy2.isNil:
+        myObjCopy2 = val
+
+    #echo cast[int](myObj)
+    #echo cast[int](myObjCopy)
+    #echo cast[int](myObjCopy2)
+
+    assert(myObjCopy == myObjCopy2) # fails
+
+
+type
+  PtrTable = object
+    counter, max: int
+    data: array[0..99, (pointer, pointer)]
+
+assert(sizeof(PtrTable) == 2*sizeof(int)+sizeof(pointer)*2*100)
+
+main()
+echo "ok"
diff --git a/tests/template/tconfusinglocal.nim b/tests/template/tconfusinglocal.nim
new file mode 100644
index 000000000..9b2cdc954
--- /dev/null
+++ b/tests/template/tconfusinglocal.nim
@@ -0,0 +1,13 @@
+
+# bug #4875
+type Bar = object
+    mFoo: int
+
+template foo(a: Bar): int = a.mFoo
+
+proc main =
+    let foo = 5 # Rename this to smth else to make it work
+    var b: Bar
+    echo b.foo
+
+main()
diff --git a/tests/test_nimscript.nims b/tests/test_nimscript.nims
index 436e990ef..2500bac73 100644
--- a/tests/test_nimscript.nims
+++ b/tests/test_nimscript.nims
@@ -14,7 +14,7 @@ import ospaths
 # import parseopt
 import parseutils
 # import pegs
-import queues
+import deques
 import sequtils
 import strutils
 import subexes
diff --git a/tests/testament/categories.nim b/tests/testament/categories.nim
index 3ed2f2196..809425653 100644
--- a/tests/testament/categories.nim
+++ b/tests/testament/categories.nim
@@ -115,6 +115,12 @@ proc dllTests(r: var TResults, cat: Category, options: string) =
 # ------------------------------ GC tests -------------------------------------
 
 proc gcTests(r: var TResults, cat: Category, options: string) =
+  template testWithNone(filename: untyped) =
+    testSpec r, makeTest("tests/gc" / filename, options &
+                  " --gc:none", cat, actionRun)
+    testSpec r, makeTest("tests/gc" / filename, options &
+                  " -d:release --gc:none", cat, actionRun)
+
   template testWithoutMs(filename: untyped) =
     testSpec r, makeTest("tests/gc" / filename, options, cat, actionRun)
     testSpec r, makeTest("tests/gc" / filename, options &
@@ -144,6 +150,7 @@ proc gcTests(r: var TResults, cat: Category, options: string) =
   test "gcleak"
   test "gcleak2"
   test "gctest"
+  testWithNone "gctest"
   test "gcleak3"
   test "gcleak4"
   # Disabled because it works and takes too long to run:
@@ -375,8 +382,14 @@ proc `&.?`(a, b: string): string =
 proc `&?.`(a, b: string): string =
   # candidate for the stdlib?
   result = if a.endswith(b): a else: a & b
+  
+proc processSingleTest(r: var TResults, cat: Category, options, test: string) =
+  let test = "tests" & DirSep &.? cat.string / test
+
+  if existsFile(test): testSpec r, makeTest(test, options, cat)
+  else: echo "[Warning] - ", test, " test does not exist"
 
-proc processCategory(r: var TResults, cat: Category, options: string, fileGlob: string = "t*.nim") =
+proc processCategory(r: var TResults, cat: Category, options: string) =
   case cat.string.normalize
   of "rodfiles":
     when false: compileRodFiles(r, cat, options)
@@ -417,5 +430,5 @@ proc processCategory(r: var TResults, cat: Category, options: string, fileGlob:
     # We can't test it because it depends on a third party.
     discard # TODO: Move untestable tests to someplace else, i.e. nimble repo.
   else:
-    for name in os.walkFiles("tests" & DirSep &.? cat.string / fileGlob):
+    for name in os.walkFiles("tests" & DirSep &.? cat.string / "t*.nim"):
       testSpec r, makeTest(name, options, cat)
diff --git a/tests/testament/tester.nim b/tests/testament/tester.nim
index 74ac58927..2734742f4 100644
--- a/tests/testament/tester.nim
+++ b/tests/testament/tester.nim
@@ -445,7 +445,7 @@ proc main() =
     let (dir, file) = splitPath(p.key.string)
     let (_, subdir) = splitPath(dir)
     var cat = Category(subdir)
-    processCategory(r, cat, p.cmdLineRest.string, file)
+    processSingleTest(r, cat, p.cmdLineRest.string, file)
   of "html":
     var commit = 0
     discard parseInt(p.cmdLineRest.string, commit)
diff --git a/tests/vm/tgorge.bat b/tests/vm/tgorge.bat
new file mode 100644
index 000000000..24d365842
--- /dev/null
+++ b/tests/vm/tgorge.bat
@@ -0,0 +1 @@
+@echo gorge test
\ No newline at end of file
diff --git a/tests/vm/tgorge.nim b/tests/vm/tgorge.nim
new file mode 100644
index 000000000..596f5d667
--- /dev/null
+++ b/tests/vm/tgorge.nim
@@ -0,0 +1,12 @@
+import os
+
+template getScriptDir(): string =
+  parentDir(instantiationInfo(-1, true).filename)
+
+const
+  execName = when defined(windows): "tgorge.bat" else: "sh tgorge.sh"
+  relOutput = gorge(execName)
+  absOutput = gorge(getScriptDir() / execName)
+
+doAssert relOutput == "gorge test"
+doAssert absOutput == "gorge test"
diff --git a/tests/vm/tgorge.sh b/tests/vm/tgorge.sh
new file mode 100644
index 000000000..ba47afeae
--- /dev/null
+++ b/tests/vm/tgorge.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo "gorge test"
\ No newline at end of file
diff --git a/tools/finish.nim b/tools/finish.nim
new file mode 100644
index 000000000..cac001d79
--- /dev/null
+++ b/tools/finish.nim
@@ -0,0 +1,167 @@
+
+# -------------- post unzip steps ---------------------------------------------
+
+import strutils, os, osproc
+
+when defined(windows):
+  import registry
+
+  proc askBool(m: string): bool =
+    stdout.write m
+    while true:
+      let answer = stdin.readLine().normalize
+      case answer
+      of "y", "yes":
+        return true
+      of "n", "no":
+        return false
+      else:
+        echo "Please type 'y' or 'n'"
+
+  proc askNumber(m: string; a, b: int): int =
+    stdout.write m
+    stdout.write " [" & $a & ".." & $b & "] "
+    while true:
+      let answer = stdin.readLine()
+      try:
+        result = parseInt answer
+        if result < a or result > b:
+          raise newException(ValueError, "number out of range")
+        break
+      except ValueError:
+        echo "Please type in a number between ", a, " and ", b
+
+  proc patchConfig(mingw: string) =
+    const
+      cfgFile = "config/nim.cfg"
+      lookFor = """#gcc.path = r"$nim\dist\mingw\bin""""
+      replacePattern = """gcc.path = r"$1""""
+    try:
+      let cfg = readFile(cfgFile)
+      let newCfg = cfg.replace(lookFor, replacePattern % mingw)
+      if newCfg == cfg:
+        echo "Could not patch 'config/nim.cfg' [Error]"
+        echo "Reason: patch substring not found:"
+        echo lookFor
+      else:
+        writeFile(cfgFile, newCfg)
+    except IOError:
+      echo "Could not access 'config/nim.cfg' [Error]"
+
+  proc addToPathEnv(e: string) =
+    let p = getUnicodeValue(r"Environment", "Path", HKEY_CURRENT_USER)
+    let x = if e.contains(Whitespace): "\"" & e & "\"" else: e
+    setUnicodeValue(r"Environment", "Path", p & ";" & x, HKEY_CURRENT_USER)
+
+  proc createShortcut(src, dest: string; icon = "") =
+    var cmd = "bin\\makelink.exe \"" & src & "\" \"\" \"" & dest &
+         ".lnk\" \"\" 1 \"" & splitFile(src).dir & "\""
+    if icon.len != 0:
+      cmd.add " \"" & icon & "\" 0"
+    discard execShellCmd(cmd)
+
+  proc createStartMenuEntry() =
+    let appdata = getEnv("APPDATA")
+    if appdata.len == 0: return
+    let dest = appdata & r"\Microsoft\Windows\Start Menu\Programs\Nim-" &
+               NimVersion
+    if dirExists(dest): return
+    if askBool("Would like to add Nim-" & NimVersion &
+               " to your start menu? (y/n) "):
+      createDir(dest)
+      createShortcut(getCurrentDir() / "tools" / "start.bat", dest / "Nim",
+                     getCurrentDir() / r"icons\nim.ico")
+      if fileExists("doc/overview.html"):
+        createShortcut(getCurrentDir() / "doc" / "html" / "overview.html",
+                       dest / "Overview")
+      if dirExists(r"dist\aporia-0.4.0"):
+        createShortcut(getCurrentDir() / r"dist\aporia-0.4.0\bin\aporia.exe",
+                       dest / "Aporia")
+
+  proc checkGccArch(mingw: string): bool =
+    let gccExe = mingw / r"gcc.exe"
+    if fileExists(gccExe):
+      try:
+        let arch = execProcess(gccExe, ["-dumpmachine"], nil, {poStdErrToStdOut,
+                                                               poUsePath})
+        when hostCPU == "i386":
+          result = arch.startsWith("i686-")
+        elif hostCPU == "amd64":
+          result = arch.startsWith("x86_64-")
+        else:
+          {.error: "Unknown CPU for Windows.".}
+      except OSError, IOError:
+        result = false
+
+  proc tryDirs(incompat: var seq[string]; dirs: varargs[string]): string =
+    let bits = $(sizeof(pointer)*8)
+    for d in dirs:
+      if dirExists d:
+        let x = expandFilename(d / "bin")
+        if checkGccArch(x): return x
+        else: incompat.add x
+      elif dirExists(d & bits):
+        let x = expandFilename((d & bits) / "bin")
+        if checkGccArch(x): return x
+        else: incompat.add x
+
+proc main() =
+  when defined(windows):
+    let desiredPath = expandFilename(getCurrentDir() / "bin")
+    let p = getUnicodeValue(r"Environment", "Path",
+      HKEY_CURRENT_USER)
+    var alreadyInPath = false
+    var mingWchoices: seq[string] = @[]
+    var incompat: seq[string] = @[]
+    for x in p.split(';'):
+      if x.len == 0: continue
+      let y = try: expandFilename(if x[0] == '"' and x[^1] == '"':
+                                    substr(x, 1, x.len-2) else: x)
+              except: ""
+      if y == desiredPath: alreadyInPath = true
+      if y.toLowerAscii.contains("mingw"):
+        if dirExists(y):
+          if checkGccArch(y): mingWchoices.add y
+          else: incompat.add y
+
+    if alreadyInPath:
+      echo "bin/nim.exe is already in your PATH [Skipping]"
+    else:
+      if askBool("nim.exe is not in your PATH environment variable.\n" &
+          "Should it be added permanently? (y/n) "):
+        addToPathEnv(desiredPath)
+    if mingWchoices.len == 0:
+      # No mingw in path, so try a few locations:
+      let alternative = tryDirs(incompat, "dist/mingw", "../mingw", r"C:\mingw")
+      if alternative.len == 0:
+        if incompat.len > 0:
+          echo "The following *incompatible* MingW installations exist"
+          for x in incompat: echo x
+        echo "No compatible MingW candidates found " &
+             "in the standard locations [Error]"
+      else:
+        if askBool("Found a MingW directory that is not in your PATH.\n" &
+                   alternative &
+                   "\nShould it be added to your PATH permanently? (y/n) "):
+          addToPathEnv(alternative)
+        elif askBool("Do you want to patch Nim's config to use this? (y/n) "):
+          patchConfig(alternative)
+    elif mingWchoices.len == 1:
+      if askBool("MingW installation found at " & mingWchoices[0] & "\n" &
+         "Do you want to patch Nim's config to use this?\n" &
+         "(Not required since it's in your PATH!) (y/n) "):
+        patchConfig(mingWchoices[0])
+    else:
+      echo "Multiple MingW installations found: "
+      for i in 0..high(mingWchoices):
+        echo "[", i, "] ", mingWchoices[i]
+      if askBool("Do you want to patch Nim's config to use one of these? (y/n) "):
+        let idx = askNumber("Which one do you want to use for Nim? ",
+            1, len(mingWchoices))
+        patchConfig(mingWchoices[idx-1])
+    createStartMenuEntry()
+  else:
+    echo("Add ", getCurrentDir(), "/bin to your PATH...")
+
+when isMainModule:
+  main()
diff --git a/tools/niminst/buildbat.tmpl b/tools/niminst/buildbat.tmpl
index 2a8da144d..278b6caea 100644
--- a/tools/niminst/buildbat.tmpl
+++ b/tools/niminst/buildbat.tmpl
@@ -1,5 +1,5 @@
 #? stdtmpl(subsChar='?') | standard
-#proc generateBuildBatchScript(c: ConfigData, winIndex, cpuIndex: int): string = 
+#proc generateBuildBatchScript(c: ConfigData, winIndex, cpuIndex: int): string =
 #  result = "@echo off\nREM Generated by niminst\n"
 SET CC=gcc
 SET LINKER=gcc
@@ -23,8 +23,8 @@ CALL %CC% %COMP_FLAGS% -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")}
 IF ERRORLEVEL 1 (GOTO:END)
 #    end for
 
-ECHO %LINKER% -o ?{"%BIN_DIR%"\toLower(c.name)}.exe ?linkCmd %LINK_FLAGS%
-CALL %LINKER% -o ?{"%BIN_DIR%"\toLower(c.name)}.exe ?linkCmd %LINK_FLAGS%
+ECHO %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS%
+CALL %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS%
 
 #  end block
 
diff --git a/tools/niminst/buildsh.tmpl b/tools/niminst/buildsh.tmpl
index 220ecdb7f..c571f4d66 100644
--- a/tools/niminst/buildsh.tmpl
+++ b/tools/niminst/buildsh.tmpl
@@ -53,7 +53,7 @@ uos=`echo $uos | tr "[:upper:]" "[:lower:]"`
 case $uos in
   *linux* )
     myos="linux"
-    LINK_FLAGS="$LINK_FLAGS -ldl -lm"
+    LINK_FLAGS="$LINK_FLAGS -ldl -lm -lrt"
     ;;
   *dragonfly* )
     myos="freebsd"
@@ -94,6 +94,9 @@ case $uos in
     myos="haiku"
     LINK_FLAGS="$LINK_FLAGS -lroot -lnetwork"
     ;;
+  *mingw* )
+    myos="windows"
+    ;;
   *)
     echo 2>&1 "Error: unknown operating system: $uos"
     exit 1
@@ -144,7 +147,7 @@ case $myos in
     $CC $COMP_FLAGS -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")}
 #        add(linkCmd, " \\\n" & changeFileExt(f, "o"))
 #      end for
-    $LINKER -o ?{"$binDir/" & toLower(c.name)} ?linkCmd $LINK_FLAGS
+    $LINKER -o ?{"$binDir/" & toLowerAscii(c.name)} ?linkCmd $LINK_FLAGS
     ;;
 #    end for
   *)
diff --git a/tools/niminst/debcreation.nim b/tools/niminst/debcreation.nim
index 36b2a29ec..0ecea132f 100644
--- a/tools/niminst/debcreation.nim
+++ b/tools/niminst/debcreation.nim
@@ -148,7 +148,7 @@ proc prepDeb*(packName, version, mtnName, mtnEmail, shortDesc, desc: string,
   ## binaries/config/docs/lib: files relative to nim's root, that need to
   ##   be installed.
 
-  let pkgName = packName.toLower()
+  let pkgName = packName.toLowerAscii()
 
   var workingDir = getTempDir() / "niminst" / "deb"
   var upstreamSource = (pkgName & "-" & version)
@@ -168,7 +168,7 @@ proc prepDeb*(packName, version, mtnName, mtnEmail, shortDesc, desc: string,
   echo("Creating necessary files in debian/")
   createDir(workingDir / upstreamSource / "debian")
 
-  template writeDebian(f, s: string): expr =
+  template writeDebian(f, s: string) =
     writeFile(workingDir / upstreamSource / "debian" / f, s)
 
   var controlFile = createControl(pkgName, makeMtn(mtnName, mtnEmail),
diff --git a/tools/niminst/deinstall.tmpl b/tools/niminst/deinstall.tmpl
index 3cdfbf45d..bba310f76 100644
--- a/tools/niminst/deinstall.tmpl
+++ b/tools/niminst/deinstall.tmpl
@@ -1,7 +1,7 @@
 #? stdtmpl(subsChar='?') | standard
 #proc generateDeinstallScript(c: ConfigData): string =
 #  result = "#! /bin/sh\n# Generated by niminst\n"
-#  var proj = c.name.toLower
+#  var proj = c.name.toLowerAscii
 
 if [ $# -eq 1 ] ; then
   case $1 in
diff --git a/tools/niminst/install.tmpl b/tools/niminst/install.tmpl
index 3f17840a8..d72b132ef 100644
--- a/tools/niminst/install.tmpl
+++ b/tools/niminst/install.tmpl
@@ -1,7 +1,7 @@
 #? stdtmpl(subsChar = '?') | standard
 #proc generateInstallScript(c: ConfigData): string =
 #  result = "#! /bin/sh\n# Generated by niminst\n"
-#  var proj = c.name.toLower
+#  var proj = c.name.toLowerAscii
 
 ## Current directory you start script from
 BASE_DIR=$(pwd)
diff --git a/tools/niminst/makefile.tmpl b/tools/niminst/makefile.tmpl
index 5c95ccda9..ce2db1c48 100644
--- a/tools/niminst/makefile.tmpl
+++ b/tools/niminst/makefile.tmpl
@@ -157,7 +157,7 @@ endif
 %.o: %.c
 	$(CC) $(COMP_FLAGS) -Ic_code -c $< -o $@
 
-?{"$(binDir)/" & toLower(c.name)}: $(oFiles)
+?{"$(binDir)/" & toLowerAscii(c.name)}: $(oFiles)
 	@mkdir -p $(binDir)
 	$(LINKER) -o $@ $^ $(LINK_FLAGS)
 	@echo "SUCCESS"
@@ -165,4 +165,4 @@ endif
 .PHONY: clean
 
 clean:
-	rm -f $(oFiles) ?{"$(binDir)/" & toLower(c.name)}
+	rm -f $(oFiles) ?{"$(binDir)/" & toLowerAscii(c.name)}
diff --git a/tools/niminst/niminst.nim b/tools/niminst/niminst.nim
index b63849a10..e0b8ad9b3 100644
--- a/tools/niminst/niminst.nim
+++ b/tools/niminst/niminst.nim
@@ -313,7 +313,7 @@ proc parseIniFile(c: var ConfigData) =
       of cfgSectionStart:
         section = normalize(k.section)
       of cfgKeyValuePair:
-        var v = k.value % c.vars
+        var v = `%`(k.value, c.vars, {useEnvironment, useEmpty})
         c.vars[k.key] = v
 
         case section
@@ -552,7 +552,7 @@ proc srcdist(c: var ConfigData) =
 # --------------------- generate inno setup -----------------------------------
 proc setupDist(c: var ConfigData) =
   let scrpt = generateInnoSetup(c)
-  let n = "build" / "install_$#_$#.iss" % [toLower(c.name), c.version]
+  let n = "build" / "install_$#_$#.iss" % [toLowerAscii(c.name), c.version]
   writeFile(n, scrpt, "\13\10")
   when defined(windows):
     if c.innosetup.path.len == 0:
@@ -569,7 +569,7 @@ proc setupDist(c: var ConfigData) =
 # --------------------- generate NSIS setup -----------------------------------
 proc setupDist2(c: var ConfigData) =
   let scrpt = generateNsisSetup(c)
-  let n = "build" / "install_$#_$#.nsi" % [toLower(c.name), c.version]
+  let n = "build" / "install_$#_$#.nsi" % [toLowerAscii(c.name), c.version]
   writeFile(n, scrpt, "\13\10")
   when defined(windows):
     if c.nsisSetup.path.len == 0:
@@ -586,7 +586,7 @@ proc setupDist2(c: var ConfigData) =
 # ------------------ generate ZIP file ---------------------------------------
 when haveZipLib:
   proc zipDist(c: var ConfigData) =
-    var proj = toLower(c.name) & "-" & c.version
+    var proj = toLowerAscii(c.name) & "-" & c.version
     var n = "$#.zip" % proj
     if c.outdir.len == 0: n = "build" / n
     else: n = c.outdir / n
@@ -618,40 +618,49 @@ when haveZipLib:
       quit("Cannot open for writing: " & n)
 
 proc xzDist(c: var ConfigData; windowsZip=false) =
-  let proj = toLower(c.name) & "-" & c.version
+  let proj = toLowerAscii(c.name) & "-" & c.version
   let tmpDir = if c.outdir.len == 0: "build" else: c.outdir
 
-  template processFile(z, dest, src) =
-    let s = src
-    let d = dest
-    echo "Copying ", s, " to ", tmpDir / d
-    let destdir = tmpdir / d.splitFile.dir
-    if not dirExists(destdir): createDir(destdir)
-    copyFileWithPermissions(s, tmpDir / d)
-
-  processFile(z, proj / buildBatFile32, "build" / buildBatFile32)
-  processFile(z, proj / buildBatFile64, "build" / buildBatFile64)
-  processFile(z, proj / buildShFile, "build" / buildShFile)
-  processFile(z, proj / makeFile, "build" / makeFile)
-  processFile(z, proj / installShFile, installShFile)
-  processFile(z, proj / deinstallShFile, deinstallShFile)
+  proc processFile(destFile, src: string) =
+    let dest = tmpDir / destFile
+    echo "Copying ", src, " to ", dest
+    if not existsFile(src):
+      echo "[Warning] Source file doesn't exist: ", src
+    let destDir = dest.splitFile.dir
+    if not dirExists(destDir): createDir(destDir)
+    copyFileWithPermissions(src, dest)
+
+  if not windowsZip and not existsFile("build" / buildBatFile32):
+    quit("No C sources found in ./build/, please build by running " &
+         "./koch csource -d:release.")
+
   if not windowsZip:
+    processFile(proj / buildBatFile32, "build" / buildBatFile32)
+    processFile(proj / buildBatFile64, "build" / buildBatFile64)
+    processFile(proj / buildShFile, "build" / buildShFile)
+    processFile(proj / makeFile, "build" / makeFile)
+    processFile(proj / installShFile, installShFile)
+    processFile(proj / deinstallShFile, deinstallShFile)
     for f in walkFiles(c.libpath / "lib/*.h"):
-      processFile(z, proj / "c_code" / extractFilename(f), f)
+      processFile(proj / "c_code" / extractFilename(f), f)
     for osA in 1..c.oses.len:
       for cpuA in 1..c.cpus.len:
         var dir = buildDir(osA, cpuA)
         for k, f in walkDir("build" / dir):
-          if k == pcFile: processFile(z, proj / dir / extractFilename(f), f)
+          if k == pcFile: processFile(proj / dir / extractFilename(f), f)
+  else:
+    for f in items(c.cat[fcWinBin]):
+      let filename = f.extractFilename
+      processFile(proj / "bin" / filename, f)
 
   let osSpecific = if windowsZip: fcWindows else: fcUnix
   for cat in items({fcConfig..fcOther, osSpecific, fcNimble}):
     echo("Current category: ", cat)
-    for f in items(c.cat[cat]): processFile(z, proj / f, f)
+    for f in items(c.cat[cat]): processFile(proj / f, f)
 
   # Copy the .nimble file over
   let nimbleFile = c.nimblePkgName & ".nimble"
-  processFile(z, proj / nimbleFile, nimbleFile)
+  processFile(proj / nimbleFile, nimbleFile)
 
   when true:
     let oldDir = getCurrentDir()
@@ -683,11 +692,11 @@ proc debDist(c: var ConfigData) =
   echo("Copying source to tmp/niminst/deb/")
   var currentSource = getCurrentDir()
   var workingDir = getTempDir() / "niminst" / "deb"
-  var upstreamSource = (c.name.toLower() & "-" & c.version)
+  var upstreamSource = (c.name.toLowerAscii() & "-" & c.version)
 
   createDir(workingDir / upstreamSource)
 
-  template copyNimDist(f, dest: string): stmt =
+  template copyNimDist(f, dest: string) =
     createDir((workingDir / upstreamSource / dest).splitFile.dir)
     copyFile(currentSource / f, workingDir / upstreamSource / dest)
 
diff --git a/tools/niminst/nsis.tmpl b/tools/niminst/nsis.tmpl
index 639a01b6b..95d4652e3 100644
--- a/tools/niminst/nsis.tmpl
+++ b/tools/niminst/nsis.tmpl
@@ -202,7 +202,7 @@
     ; Shortcuts
     #  if d.len >= 6:
     #    let startMenuEntry = d[5]
-    #    let e = splitFile(startMenuEntry).name.capitalize
+    #    let e = splitFile(startMenuEntry).name.capitalizeAscii
       !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
         CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\?{e}.lnk" "$INSTDIR\?dir\?{startMenuEntry.toWin}"
       !insertmacro MUI_STARTMENU_WRITE_END
diff --git a/tools/nimsuggest/nimsuggest.nim b/tools/nimsuggest/nimsuggest.nim
new file mode 100644
index 000000000..b5e7b282f
--- /dev/null
+++ b/tools/nimsuggest/nimsuggest.nim
@@ -0,0 +1,475 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2016 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Nimsuggest is a tool that helps to give editors IDE like capabilities.
+
+import strutils, os, parseopt, parseutils, sequtils, net, rdstdin, sexp
+# Do NOT import suggest. It will lead to wierd bugs with
+# suggestionResultHook, because suggest.nim is included by sigmatch.
+# So we import that one instead.
+import compiler/options, compiler/commands, compiler/modules, compiler/sem,
+  compiler/passes, compiler/passaux, compiler/msgs, compiler/nimconf,
+  compiler/extccomp, compiler/condsyms, compiler/lists,
+  compiler/sigmatch, compiler/ast, compiler/scriptconfig,
+  compiler/idents, compiler/modulegraphs
+
+when defined(windows):
+  import winlean
+else:
+  import posix
+
+const DummyEof = "!EOF!"
+const Usage = """
+Nimsuggest - Tool to give every editor IDE like capabilities for Nim
+Usage:
+  nimsuggest [options] projectfile.nim
+
+Options:
+  --port:PORT             port, by default 6000
+  --address:HOST          binds to that address, by default ""
+  --stdin                 read commands from stdin and write results to
+                          stdout instead of using sockets
+  --epc                   use emacs epc mode
+  --debug                 enable debug output
+  --log                   enable verbose logging to nimsuggest.log file
+  --v2                    use version 2 of the protocol; more features and
+                          much faster
+  --tester                implies --v2 and --stdin and outputs a line
+                          '""" & DummyEof & """' for the tester
+
+The server then listens to the connection and takes line-based commands.
+
+In addition, all command line options of Nim that do not affect code generation
+are supported.
+"""
+type
+  Mode = enum mstdin, mtcp, mepc
+
+var
+  gPort = 6000.Port
+  gAddress = ""
+  gMode: Mode
+  gEmitEof: bool # whether we write '!EOF!' dummy lines
+  gLogging = false
+
+const
+  seps = {':', ';', ' ', '\t'}
+  Help = "usage: sug|con|def|use|dus|chk|mod|highlight|outline file.nim[;dirtyfile.nim]:line:col\n" &
+         "type 'quit' to quit\n" &
+         "type 'debug' to toggle debug mode on/off\n" &
+         "type 'terse' to toggle terse mode on/off"
+
+type
+  EUnexpectedCommand = object of Exception
+
+proc logStr(line: string) =
+  var f: File
+  if open(f, getHomeDir() / "nimsuggest.log", fmAppend):
+    f.writeLine(line)
+    f.close()
+
+proc parseQuoted(cmd: string; outp: var string; start: int): int =
+  var i = start
+  i += skipWhitespace(cmd, i)
+  if cmd[i] == '"':
+    i += parseUntil(cmd, outp, '"', i+1)+2
+  else:
+    i += parseUntil(cmd, outp, seps, i)
+  result = i
+
+proc sexp(s: IdeCmd|TSymKind): SexpNode = sexp($s)
+
+proc sexp(s: Suggest): SexpNode =
+  # If you change the order here, make sure to change it over in
+  # nim-mode.el too.
+  result = convertSexp([
+    s.section,
+    s.symkind,
+    s.qualifiedPath.map(newSString),
+    s.filePath,
+    s.forth,
+    s.line,
+    s.column,
+    s.doc
+  ])
+
+proc sexp(s: seq[Suggest]): SexpNode =
+  result = newSList()
+  for sug in s:
+    result.add(sexp(sug))
+
+proc listEpc(): SexpNode =
+  # This function is called from Emacs to show available options.
+  let
+    argspecs = sexp("file line column dirtyfile".split(" ").map(newSSymbol))
+    docstring = sexp("line starts at 1, column at 0, dirtyfile is optional")
+  result = newSList()
+  for command in ["sug", "con", "def", "use", "dus", "chk", "mod"]:
+    let
+      cmd = sexp(command)
+      methodDesc = newSList()
+    methodDesc.add(cmd)
+    methodDesc.add(argspecs)
+    methodDesc.add(docstring)
+    result.add(methodDesc)
+
+proc findNode(n: PNode): PSym =
+  #echo "checking node ", n.info
+  if n.kind == nkSym:
+    if isTracked(n.info, n.sym.name.s.len): return n.sym
+  else:
+    for i in 0 ..< safeLen(n):
+      let res = n.sons[i].findNode
+      if res != nil: return res
+
+proc symFromInfo(graph: ModuleGraph; gTrackPos: TLineInfo): PSym =
+  let m = graph.getModule(gTrackPos.fileIndex)
+  #echo m.isNil, " I knew it ", gTrackPos.fileIndex
+  if m != nil and m.ast != nil:
+    result = m.ast.findNode
+
+proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int;
+             graph: ModuleGraph; cache: IdentCache) =
+  if gLogging:
+    logStr("cmd: " & $cmd & ", file: " & file & ", dirtyFile: " & dirtyfile & "[" & $line & ":" & $col & "]")
+  gIdeCmd = cmd
+  if cmd == ideUse and suggestVersion != 2:
+    graph.resetAllModules()
+  var isKnownFile = true
+  let dirtyIdx = file.fileInfoIdx(isKnownFile)
+
+  if dirtyfile.len != 0: msgs.setDirtyFile(dirtyIdx, dirtyfile)
+  else: msgs.setDirtyFile(dirtyIdx, nil)
+
+  gTrackPos = newLineInfo(dirtyIdx, line, col)
+  gErrorCounter = 0
+  if suggestVersion < 2:
+    usageSym = nil
+  if not isKnownFile:
+    graph.compileProject(cache)
+  if suggestVersion == 2 and gIdeCmd in {ideUse, ideDus} and
+      dirtyfile.len == 0:
+    discard "no need to recompile anything"
+  else:
+    let modIdx = graph.parentModule(dirtyIdx)
+    graph.markDirty dirtyIdx
+    graph.markClientsDirty dirtyIdx
+    if gIdeCmd != ideMod:
+      graph.compileProject(cache, modIdx)
+  if gIdeCmd in {ideUse, ideDus}:
+    let u = if suggestVersion >= 2: graph.symFromInfo(gTrackPos) else: usageSym
+    if u != nil:
+      listUsages(u)
+    else:
+      localError(gTrackPos, "found no symbol at this position " & $gTrackPos)
+
+proc executeEpc(cmd: IdeCmd, args: SexpNode;
+                graph: ModuleGraph; cache: IdentCache) =
+  let
+    file = args[0].getStr
+    line = args[1].getNum
+    column = args[2].getNum
+  var dirtyfile = ""
+  if len(args) > 3:
+    dirtyfile = args[3].getStr(nil)
+  execute(cmd, file, dirtyfile, int(line), int(column), graph, cache)
+
+proc returnEpc(socket: var Socket, uid: BiggestInt, s: SexpNode|string,
+               return_symbol = "return") =
+  let response = $convertSexp([newSSymbol(return_symbol), uid, s])
+  socket.send(toHex(len(response), 6))
+  socket.send(response)
+
+template sendEpc(results: typed, tdef, hook: untyped) =
+  hook = proc (s: tdef) =
+    results.add(
+      # Put newlines to parse output by flycheck-nim.el
+      when results is string: s & "\n"
+      else: s
+    )
+
+  executeEpc(gIdeCmd, args, graph, cache)
+  let res = sexp(results)
+  if gLogging:
+    logStr($res)
+  returnEPC(client, uid, res)
+
+template checkSanity(client, sizeHex, size, messageBuffer: typed) =
+  if client.recv(sizeHex, 6) != 6:
+    raise newException(ValueError, "didn't get all the hexbytes")
+  if parseHex(sizeHex, size) == 0:
+    raise newException(ValueError, "invalid size hex: " & $sizeHex)
+  if client.recv(messageBuffer, size) != size:
+    raise newException(ValueError, "didn't get all the bytes")
+
+template setVerbosity(level: typed) =
+  gVerbosity = level
+  gNotes = NotesVerbosity[gVerbosity]
+
+proc connectToNextFreePort(server: Socket, host: string): Port =
+  server.bindaddr(Port(0), host)
+  let (_, port) = server.getLocalAddr
+  result = port
+
+proc parseCmdLine(cmd: string; graph: ModuleGraph; cache: IdentCache) =
+  template toggle(sw) =
+    if sw in gGlobalOptions:
+      excl(gGlobalOptions, sw)
+    else:
+      incl(gGlobalOptions, sw)
+    return
+
+  template err() =
+    echo Help
+    return
+
+  var opc = ""
+  var i = parseIdent(cmd, opc, 0)
+  case opc.normalize
+  of "sug": gIdeCmd = ideSug
+  of "con": gIdeCmd = ideCon
+  of "def": gIdeCmd = ideDef
+  of "use": gIdeCmd = ideUse
+  of "dus": gIdeCmd = ideDus
+  of "mod": gIdeCmd = ideMod
+  of "chk":
+    gIdeCmd = ideChk
+    incl(gGlobalOptions, optIdeDebug)
+  of "highlight": gIdeCmd = ideHighlight
+  of "outline": gIdeCmd = ideOutline
+  of "quit": quit()
+  of "debug": toggle optIdeDebug
+  of "terse": toggle optIdeTerse
+  else: err()
+  var dirtyfile = ""
+  var orig = ""
+  i = parseQuoted(cmd, orig, i)
+  if cmd[i] == ';':
+    i = parseQuoted(cmd, dirtyfile, i+1)
+  i += skipWhile(cmd, seps, i)
+  var line = -1
+  var col = 0
+  i += parseInt(cmd, line, i)
+  i += skipWhile(cmd, seps, i)
+  i += parseInt(cmd, col, i)
+
+  execute(gIdeCmd, orig, dirtyfile, line, col-1, graph, cache)
+
+proc serveStdin(graph: ModuleGraph; cache: IdentCache) =
+  if gEmitEof:
+    echo DummyEof
+    while true:
+      let line = readLine(stdin)
+      parseCmdLine line, graph, cache
+      echo DummyEof
+      flushFile(stdout)
+  else:
+    echo Help
+    var line = ""
+    while readLineFromStdin("> ", line):
+      parseCmdLine line, graph, cache
+      echo ""
+      flushFile(stdout)
+
+proc serveTcp(graph: ModuleGraph; cache: IdentCache) =
+  var server = newSocket()
+  server.bindAddr(gPort, gAddress)
+  var inp = "".TaintedString
+  server.listen()
+
+  while true:
+    var stdoutSocket = newSocket()
+    msgs.writelnHook = proc (line: string) =
+      stdoutSocket.send(line & "\c\L")
+
+    accept(server, stdoutSocket)
+
+    stdoutSocket.readLine(inp)
+    parseCmdLine inp.string, graph, cache
+
+    stdoutSocket.send("\c\L")
+    stdoutSocket.close()
+
+proc serveEpc(server: Socket; graph: ModuleGraph; cache: IdentCache) =
+  var client = newSocket()
+  # Wait for connection
+  accept(server, client)
+  if gLogging:
+    var it = searchPaths.head
+    while it != nil:
+      logStr(PStrEntry(it).data)
+      it = it.next
+    msgs.writelnHook = proc (line: string) = logStr(line)
+
+  while true:
+    var
+      sizeHex = ""
+      size = 0
+      messageBuffer = ""
+    checkSanity(client, sizeHex, size, messageBuffer)
+    let
+      message = parseSexp($messageBuffer)
+      epcAPI = message[0].getSymbol
+    case epcAPI:
+    of "call":
+      let
+        uid = message[1].getNum
+        args = message[3]
+
+      gIdeCmd = parseIdeCmd(message[2].getSymbol)
+      case gIdeCmd
+      of ideChk:
+        setVerbosity(1)
+        # Use full path because other emacs plugins depends it
+        gListFullPaths = true
+        incl(gGlobalOptions, optIdeDebug)
+        var hints_or_errors = ""
+        sendEpc(hints_or_errors, string, msgs.writelnHook)
+      of ideSug, ideCon, ideDef, ideUse, ideDus, ideOutline, ideHighlight:
+        setVerbosity(0)
+        var suggests: seq[Suggest] = @[]
+        sendEpc(suggests, Suggest, suggestionResultHook)
+      else: discard
+    of "methods":
+      returnEpc(client, message[1].getNum, listEPC())
+    of "epc-error":
+      stderr.writeline("recieved epc error: " & $messageBuffer)
+      raise newException(IOError, "epc error")
+    else:
+      let errMessage = case epcAPI
+                       of "return", "return-error":
+                         "no return expected"
+                       else:
+                         "unexpected call: " & epcAPI
+      raise newException(EUnexpectedCommand, errMessage)
+
+proc mainCommand(graph: ModuleGraph; cache: IdentCache) =
+  clearPasses()
+  registerPass verbosePass
+  registerPass semPass
+  gCmd = cmdIdeTools
+  incl gGlobalOptions, optCaasEnabled
+  isServing = true
+  wantMainModule()
+  appendStr(searchPaths, options.libpath)
+  #if gProjectFull.len != 0:
+    # current path is always looked first for modules
+  #  prependStr(searchPaths, gProjectPath)
+
+  # do not stop after the first error:
+  msgs.gErrorMax = high(int)
+
+  case gMode
+  of mstdin:
+    compileProject(graph, cache)
+    #modules.gFuzzyGraphChecking = false
+    serveStdin(graph, cache)
+  of mtcp:
+    # until somebody accepted the connection, produce no output (logging is too
+    # slow for big projects):
+    msgs.writelnHook = proc (msg: string) = discard
+    compileProject(graph, cache)
+    #modules.gFuzzyGraphChecking = false
+    serveTcp(graph, cache)
+  of mepc:
+    var server = newSocket()
+    let port = connectToNextFreePort(server, "localhost")
+    server.listen()
+    echo port
+    compileProject(graph, cache)
+    serveEpc(server, graph, cache)
+
+proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
+  var p = parseopt.initOptParser(cmd)
+  while true:
+    parseopt.next(p)
+    case p.kind
+    of cmdEnd: break
+    of cmdLongoption, cmdShortOption:
+      case p.key.normalize
+      of "port":
+        gPort = parseInt(p.val).Port
+        gMode = mtcp
+      of "address":
+        gAddress = p.val
+        gMode = mtcp
+      of "stdin": gMode = mstdin
+      of "epc":
+        gMode = mepc
+        gVerbosity = 0          # Port number gotta be first.
+      of "debug":
+        incl(gGlobalOptions, optIdeDebug)
+      of "v2":
+        suggestVersion = 2
+      of "tester":
+        suggestVersion = 2
+        gMode = mstdin
+        gEmitEof = true
+      of "log":
+        gLogging = true
+      else: processSwitch(pass, p)
+    of cmdArgument:
+      options.gProjectName = unixToNativePath(p.key)
+      # if processArgument(pass, p, argsCount): break
+
+proc handleCmdLine(cache: IdentCache) =
+  if paramCount() == 0:
+    stdout.writeline(Usage)
+  else:
+    processCmdLine(passCmd1, "")
+    if gMode != mstdin:
+      msgs.writelnHook = proc (msg: string) = discard
+    if gProjectName != "":
+      try:
+        gProjectFull = canonicalizePath(gProjectName)
+      except OSError:
+        gProjectFull = gProjectName
+      var p = splitFile(gProjectFull)
+      gProjectPath = canonicalizePath p.dir
+      gProjectName = p.name
+    else:
+      gProjectPath = canonicalizePath getCurrentDir()
+
+    # Find Nim's prefix dir.
+    let binaryPath = findExe("nim")
+    if binaryPath == "":
+      raise newException(IOError,
+          "Cannot find Nim standard library: Nim compiler not in PATH")
+    gPrefixDir = binaryPath.splitPath().head.parentDir()
+    #msgs.writelnHook = proc (line: string) = logStr(line)
+
+    loadConfigs(DefaultConfig, cache) # load all config files
+    # now process command line arguments again, because some options in the
+    # command line can overwite the config file's settings
+    options.command = "nimsuggest"
+    let scriptFile = gProjectFull.changeFileExt("nims")
+    if fileExists(scriptFile):
+      runNimScript(cache, scriptFile, freshDefines=false)
+      # 'nim foo.nims' means to just run the NimScript file and do nothing more:
+      if scriptFile == gProjectFull: return
+    elif fileExists(gProjectPath / "config.nims"):
+      # directory wide NimScript file
+      runNimScript(cache, gProjectPath / "config.nims", freshDefines=false)
+
+    extccomp.initVars()
+    processCmdLine(passCmd2, "")
+
+    let graph = newModuleGraph()
+    graph.suggestMode = true
+    mainCommand(graph, cache)
+
+when false:
+  proc quitCalled() {.noconv.} =
+    writeStackTrace()
+
+  addQuitProc(quitCalled)
+
+condsyms.initDefines()
+defineSymbol "nimsuggest"
+handleCmdline(newIdentCache())
diff --git a/tools/nimsuggest/nimsuggest.nim.cfg b/tools/nimsuggest/nimsuggest.nim.cfg
new file mode 100644
index 000000000..949bd18e8
--- /dev/null
+++ b/tools/nimsuggest/nimsuggest.nim.cfg
@@ -0,0 +1,16 @@
+# Special configuration file for the Nim project
+
+gc:markAndSweep
+
+hint[XDeclaredButNotUsed]:off
+
+path:"$lib/packages/docutils"
+
+define:useStdoutAsStdmsg
+define:nimsuggest
+
+#cs:partial
+#define:useNodeIds
+#define:booting
+#define:noDocgen
+--path:"$nim"
diff --git a/tools/nimsuggest/nimsuggest.nimble b/tools/nimsuggest/nimsuggest.nimble
new file mode 100644
index 000000000..3651e12bd
--- /dev/null
+++ b/tools/nimsuggest/nimsuggest.nimble
@@ -0,0 +1,11 @@
+[Package]
+name          = "nimsuggest"
+version       = "0.1.0"
+author        = "Andreas Rumpf"
+description   = "Tool for providing auto completion data for Nim source code."
+license       = "MIT"
+
+bin = "nimsuggest"
+
+[Deps]
+Requires: "nim >= 0.11.2, compiler#head"
diff --git a/tools/nimsuggest/sexp.nim b/tools/nimsuggest/sexp.nim
new file mode 100644
index 000000000..cf08111d7
--- /dev/null
+++ b/tools/nimsuggest/sexp.nim
@@ -0,0 +1,697 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2015 Andreas Rumpf, Dominik Picheta
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+import
+  hashes, strutils, lexbase, streams, unicode, macros
+
+type
+  SexpEventKind* = enum  ## enumeration of all events that may occur when parsing
+    sexpError,           ## an error occurred during parsing
+    sexpEof,             ## end of file reached
+    sexpString,          ## a string literal
+    sexpSymbol,          ## a symbol
+    sexpInt,             ## an integer literal
+    sexpFloat,           ## a float literal
+    sexpNil,             ## the value ``nil``
+    sexpDot,             ## the dot to separate car/cdr
+    sexpListStart,       ## start of a list: the ``(`` token
+    sexpListEnd,         ## end of a list: the ``)`` token
+
+  TTokKind = enum        # must be synchronized with SexpEventKind!
+    tkError,
+    tkEof,
+    tkString,
+    tkSymbol,
+    tkInt,
+    tkFloat,
+    tkNil,
+    tkDot,
+    tkParensLe,
+    tkParensRi
+    tkSpace
+
+  SexpError* = enum        ## enumeration that lists all errors that can occur
+    errNone,               ## no error
+    errInvalidToken,       ## invalid token
+    errParensRiExpected,    ## ``)`` expected
+    errQuoteExpected,      ## ``"`` expected
+    errEofExpected,        ## EOF expected
+
+  SexpParser* = object of BaseLexer ## the parser object.
+    a: string
+    tok: TTokKind
+    kind: SexpEventKind
+    err: SexpError
+
+const
+  errorMessages: array[SexpError, string] = [
+    "no error",
+    "invalid token",
+    "')' expected",
+    "'\"' or \"'\" expected",
+    "EOF expected",
+  ]
+  tokToStr: array[TTokKind, string] = [
+    "invalid token",
+    "EOF",
+    "string literal",
+    "symbol",
+    "int literal",
+    "float literal",
+    "nil",
+    ".",
+    "(", ")", "space"
+  ]
+
+proc close*(my: var SexpParser) {.inline.} =
+  ## closes the parser `my` and its associated input stream.
+  lexbase.close(my)
+
+proc str*(my: SexpParser): string {.inline.} =
+  ## returns the character data for the events: ``sexpInt``, ``sexpFloat``,
+  ## ``sexpString``
+  assert(my.kind in {sexpInt, sexpFloat, sexpString})
+  result = my.a
+
+proc getInt*(my: SexpParser): BiggestInt {.inline.} =
+  ## returns the number for the event: ``sexpInt``
+  assert(my.kind == sexpInt)
+  result = parseBiggestInt(my.a)
+
+proc getFloat*(my: SexpParser): float {.inline.} =
+  ## returns the number for the event: ``sexpFloat``
+  assert(my.kind == sexpFloat)
+  result = parseFloat(my.a)
+
+proc kind*(my: SexpParser): SexpEventKind {.inline.} =
+  ## returns the current event type for the SEXP parser
+  result = my.kind
+
+proc getColumn*(my: SexpParser): int {.inline.} =
+  ## get the current column the parser has arrived at.
+  result = getColNumber(my, my.bufpos)
+
+proc getLine*(my: SexpParser): int {.inline.} =
+  ## get the current line the parser has arrived at.
+  result = my.lineNumber
+
+proc errorMsg*(my: SexpParser): string =
+  ## returns a helpful error message for the event ``sexpError``
+  assert(my.kind == sexpError)
+  result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), errorMessages[my.err]]
+
+proc errorMsgExpected*(my: SexpParser, e: string): string =
+  ## returns an error message "`e` expected" in the same format as the
+  ## other error messages
+  result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), e & " expected"]
+
+proc handleHexChar(c: char, x: var int): bool =
+  result = true # Success
+  case c
+  of '0'..'9': x = (x shl 4) or (ord(c) - ord('0'))
+  of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10)
+  of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10)
+  else: result = false # error
+
+proc parseString(my: var SexpParser): TTokKind =
+  result = tkString
+  var pos = my.bufpos + 1
+  var buf = my.buf
+  while true:
+    case buf[pos]
+    of '\0':
+      my.err = errQuoteExpected
+      result = tkError
+      break
+    of '"':
+      inc(pos)
+      break
+    of '\\':
+      case buf[pos+1]
+      of '\\', '"', '\'', '/':
+        add(my.a, buf[pos+1])
+        inc(pos, 2)
+      of 'b':
+        add(my.a, '\b')
+        inc(pos, 2)
+      of 'f':
+        add(my.a, '\f')
+        inc(pos, 2)
+      of 'n':
+        add(my.a, '\L')
+        inc(pos, 2)
+      of 'r':
+        add(my.a, '\C')
+        inc(pos, 2)
+      of 't':
+        add(my.a, '\t')
+        inc(pos, 2)
+      of 'u':
+        inc(pos, 2)
+        var r: int
+        if handleHexChar(buf[pos], r): inc(pos)
+        if handleHexChar(buf[pos], r): inc(pos)
+        if handleHexChar(buf[pos], r): inc(pos)
+        if handleHexChar(buf[pos], r): inc(pos)
+        add(my.a, toUTF8(Rune(r)))
+      else:
+        # don't bother with the error
+        add(my.a, buf[pos])
+        inc(pos)
+    of '\c':
+      pos = lexbase.handleCR(my, pos)
+      buf = my.buf
+      add(my.a, '\c')
+    of '\L':
+      pos = lexbase.handleLF(my, pos)
+      buf = my.buf
+      add(my.a, '\L')
+    else:
+      add(my.a, buf[pos])
+      inc(pos)
+  my.bufpos = pos # store back
+
+proc parseNumber(my: var SexpParser) =
+  var pos = my.bufpos
+  var buf = my.buf
+  if buf[pos] == '-':
+    add(my.a, '-')
+    inc(pos)
+  if buf[pos] == '.':
+    add(my.a, "0.")
+    inc(pos)
+  else:
+    while buf[pos] in Digits:
+      add(my.a, buf[pos])
+      inc(pos)
+    if buf[pos] == '.':
+      add(my.a, '.')
+      inc(pos)
+  # digits after the dot:
+  while buf[pos] in Digits:
+    add(my.a, buf[pos])
+    inc(pos)
+  if buf[pos] in {'E', 'e'}:
+    add(my.a, buf[pos])
+    inc(pos)
+    if buf[pos] in {'+', '-'}:
+      add(my.a, buf[pos])
+      inc(pos)
+    while buf[pos] in Digits:
+      add(my.a, buf[pos])
+      inc(pos)
+  my.bufpos = pos
+
+proc parseSymbol(my: var SexpParser) =
+  var pos = my.bufpos
+  var buf = my.buf
+  if buf[pos] in IdentStartChars:
+    while buf[pos] in IdentChars:
+      add(my.a, buf[pos])
+      inc(pos)
+  my.bufpos = pos
+
+proc getTok(my: var SexpParser): TTokKind =
+  setLen(my.a, 0)
+  case my.buf[my.bufpos]
+  of '-', '0'..'9': # numbers that start with a . are not parsed
+                    # correctly.
+    parseNumber(my)
+    if {'.', 'e', 'E'} in my.a:
+      result = tkFloat
+    else:
+      result = tkInt
+  of '"': #" # gotta fix nim-mode
+    result = parseString(my)
+  of '(':
+    inc(my.bufpos)
+    result = tkParensLe
+  of ')':
+    inc(my.bufpos)
+    result = tkParensRi
+  of '\0':
+    result = tkEof
+  of 'a'..'z', 'A'..'Z', '_':
+    parseSymbol(my)
+    if my.a == "nil":
+      result = tkNil
+    else:
+      result = tkSymbol
+  of ' ':
+    result = tkSpace
+    inc(my.bufpos)
+  of '.':
+    result = tkDot
+    inc(my.bufpos)
+  else:
+    inc(my.bufpos)
+    result = tkError
+  my.tok = result
+
+# ------------- higher level interface ---------------------------------------
+
+type
+  SexpNodeKind* = enum ## possible SEXP node types
+    SNil,
+    SInt,
+    SFloat,
+    SString,
+    SSymbol,
+    SList,
+    SCons
+
+  SexpNode* = ref SexpNodeObj ## SEXP node
+  SexpNodeObj* {.acyclic.} = object
+    case kind*: SexpNodeKind
+    of SString:
+      str*: string
+    of SSymbol:
+      symbol*: string
+    of SInt:
+      num*: BiggestInt
+    of SFloat:
+      fnum*: float
+    of SList:
+      elems*: seq[SexpNode]
+    of SCons:
+      car: SexpNode
+      cdr: SexpNode
+    of SNil:
+      discard
+
+  Cons = tuple[car: SexpNode, cdr: SexpNode]
+
+  SexpParsingError* = object of ValueError ## is raised for a SEXP error
+
+proc raiseParseErr*(p: SexpParser, msg: string) {.noinline, noreturn.} =
+  ## raises an `ESexpParsingError` exception.
+  raise newException(SexpParsingError, errorMsgExpected(p, msg))
+
+proc newSString*(s: string): SexpNode {.procvar.}=
+  ## Creates a new `SString SexpNode`.
+  new(result)
+  result.kind = SString
+  result.str = s
+
+proc newSStringMove(s: string): SexpNode =
+  new(result)
+  result.kind = SString
+  shallowCopy(result.str, s)
+
+proc newSInt*(n: BiggestInt): SexpNode {.procvar.} =
+  ## Creates a new `SInt SexpNode`.
+  new(result)
+  result.kind = SInt
+  result.num  = n
+
+proc newSFloat*(n: float): SexpNode {.procvar.} =
+  ## Creates a new `SFloat SexpNode`.
+  new(result)
+  result.kind = SFloat
+  result.fnum  = n
+
+proc newSNil*(): SexpNode {.procvar.} =
+  ## Creates a new `SNil SexpNode`.
+  new(result)
+
+proc newSCons*(car, cdr: SexpNode): SexpNode {.procvar.} =
+  ## Creates a new `SCons SexpNode`
+  new(result)
+  result.kind = SCons
+  result.car = car
+  result.cdr = cdr
+
+proc newSList*(): SexpNode {.procvar.} =
+  ## Creates a new `SList SexpNode`
+  new(result)
+  result.kind = SList
+  result.elems = @[]
+
+proc newSSymbol*(s: string): SexpNode {.procvar.} =
+  new(result)
+  result.kind = SSymbol
+  result.symbol = s
+
+proc newSSymbolMove(s: string): SexpNode =
+  new(result)
+  result.kind = SSymbol
+  shallowCopy(result.symbol, s)
+
+proc getStr*(n: SexpNode, default: string = ""): string =
+  ## Retrieves the string value of a `SString SexpNode`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``SString``.
+  if n.kind != SString: return default
+  else: return n.str
+
+proc getNum*(n: SexpNode, default: BiggestInt = 0): BiggestInt =
+  ## Retrieves the int value of a `SInt SexpNode`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``SInt``.
+  if n.kind != SInt: return default
+  else: return n.num
+
+proc getFNum*(n: SexpNode, default: float = 0.0): float =
+  ## Retrieves the float value of a `SFloat SexpNode`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``SFloat``.
+  if n.kind != SFloat: return default
+  else: return n.fnum
+
+proc getSymbol*(n: SexpNode, default: string = ""): string =
+  ## Retrieves the int value of a `SList SexpNode`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``SList``.
+  if n.kind != SSymbol: return default
+  else: return n.symbol
+
+proc getElems*(n: SexpNode, default: seq[SexpNode] = @[]): seq[SexpNode] =
+  ## Retrieves the int value of a `SList SexpNode`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``SList``.
+  if n.kind == SNil: return @[]
+  elif n.kind != SList: return default
+  else: return n.elems
+
+proc getCons*(n: SexpNode, defaults: Cons = (newSNil(), newSNil())): Cons =
+  ## Retrieves the cons value of a `SList SexpNode`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``SList``.
+  if n.kind == SCons: return (n.car, n.cdr)
+  elif n.kind == SList: return (n.elems[0], n.elems[1])
+  else: return defaults
+
+proc sexp*(s: string): SexpNode =
+  ## Generic constructor for SEXP data. Creates a new `SString SexpNode`.
+  new(result)
+  result.kind = SString
+  result.str = s
+
+proc sexp*(n: BiggestInt): SexpNode =
+  ## Generic constructor for SEXP data. Creates a new `SInt SexpNode`.
+  new(result)
+  result.kind = SInt
+  result.num  = n
+
+proc sexp*(n: float): SexpNode =
+  ## Generic constructor for SEXP data. Creates a new `SFloat SexpNode`.
+  new(result)
+  result.kind = SFloat
+  result.fnum  = n
+
+proc sexp*(b: bool): SexpNode =
+  ## Generic constructor for SEXP data. Creates a new `SSymbol
+  ## SexpNode` with value t or `SNil SexpNode`.
+  new(result)
+  if b:
+    result.kind = SSymbol
+    result.symbol = "t"
+  else:
+    result.kind = SNil
+
+proc sexp*(elements: openArray[SexpNode]): SexpNode =
+  ## Generic constructor for SEXP data. Creates a new `SList SexpNode`
+  new(result)
+  result.kind = SList
+  newSeq(result.elems, elements.len)
+  for i, p in pairs(elements): result.elems[i] = p
+
+proc sexp*(s: SexpNode): SexpNode =
+  result = s
+
+proc toSexp(x: NimNode): NimNode {.compiletime.} =
+  case x.kind
+  of nnkBracket:
+    result = newNimNode(nnkBracket)
+    for i in 0 .. <x.len:
+      result.add(toSexp(x[i]))
+
+  else:
+    result = x
+
+  result = prefix(result, "sexp")
+
+macro convertSexp*(x: untyped): untyped =
+  ## Convert an expression to a SexpNode directly, without having to specify
+  ## `%` for every element.
+  result = toSexp(x)
+
+proc `==`* (a,b: SexpNode): bool =
+  ## Check two nodes for equality
+  if a.isNil:
+    if b.isNil: return true
+    return false
+  elif b.isNil or a.kind != b.kind:
+    return false
+  else:
+    return case a.kind
+    of SString:
+      a.str == b.str
+    of SInt:
+      a.num == b.num
+    of SFloat:
+      a.fnum == b.fnum
+    of SNil:
+      true
+    of SList:
+      a.elems == b.elems
+    of SSymbol:
+      a.symbol == b.symbol
+    of SCons:
+      a.car == b.car and a.cdr == b.cdr
+
+proc hash* (n:SexpNode): Hash =
+  ## Compute the hash for a SEXP node
+  case n.kind
+  of SList:
+    result = hash(n.elems)
+  of SInt:
+    result = hash(n.num)
+  of SFloat:
+    result = hash(n.fnum)
+  of SString:
+    result = hash(n.str)
+  of SNil:
+    result = hash(0)
+  of SSymbol:
+    result = hash(n.symbol)
+  of SCons:
+    result = hash(n.car) !& hash(n.cdr)
+
+proc len*(n: SexpNode): int =
+  ## If `n` is a `SList`, it returns the number of elements.
+  ## If `n` is a `JObject`, it returns the number of pairs.
+  ## Else it returns 0.
+  case n.kind
+  of SList: result = n.elems.len
+  else: discard
+
+proc `[]`*(node: SexpNode, index: int): SexpNode =
+  ## Gets the node at `index` in a List. Result is undefined if `index`
+  ## is out of bounds
+  assert(not isNil(node))
+  assert(node.kind == SList)
+  return node.elems[index]
+
+proc add*(father, child: SexpNode) =
+  ## Adds `child` to a SList node `father`.
+  assert father.kind == SList
+  father.elems.add(child)
+
+# ------------- pretty printing ----------------------------------------------
+
+proc indent(s: var string, i: int) =
+  s.add(spaces(i))
+
+proc newIndent(curr, indent: int, ml: bool): int =
+  if ml: return curr + indent
+  else: return indent
+
+proc nl(s: var string, ml: bool) =
+  if ml: s.add("\n")
+
+proc escapeJson*(s: string): string =
+  ## Converts a string `s` to its JSON representation.
+  result = newStringOfCap(s.len + s.len shr 3)
+  result.add("\"")
+  for x in runes(s):
+    var r = int(x)
+    if r >= 32 and r <= 127:
+      var c = chr(r)
+      case c
+      of '"': result.add("\\\"") #" # gotta fix nim-mode
+      of '\\': result.add("\\\\")
+      else: result.add(c)
+    else:
+      result.add("\\u")
+      result.add(toHex(r, 4))
+  result.add("\"")
+
+proc copy*(p: SexpNode): SexpNode =
+  ## Performs a deep copy of `a`.
+  case p.kind
+  of SString:
+    result = newSString(p.str)
+  of SInt:
+    result = newSInt(p.num)
+  of SFloat:
+    result = newSFloat(p.fnum)
+  of SNil:
+    result = newSNil()
+  of SSymbol:
+    result = newSSymbol(p.symbol)
+  of SList:
+    result = newSList()
+    for i in items(p.elems):
+      result.elems.add(copy(i))
+  of SCons:
+    result = newSCons(copy(p.car), copy(p.cdr))
+
+proc toPretty(result: var string, node: SexpNode, indent = 2, ml = true,
+              lstArr = false, currIndent = 0) =
+  case node.kind
+  of SString:
+    if lstArr: result.indent(currIndent)
+    result.add(escapeJson(node.str))
+  of SInt:
+    if lstArr: result.indent(currIndent)
+    result.add($node.num)
+  of SFloat:
+    if lstArr: result.indent(currIndent)
+    result.add($node.fnum)
+  of SNil:
+    if lstArr: result.indent(currIndent)
+    result.add("nil")
+  of SSymbol:
+    if lstArr: result.indent(currIndent)
+    result.add($node.symbol)
+  of SList:
+    if lstArr: result.indent(currIndent)
+    if len(node.elems) != 0:
+      result.add("(")
+      result.nl(ml)
+      for i in 0..len(node.elems)-1:
+        if i > 0:
+          result.add(" ")
+          result.nl(ml) # New Line
+        toPretty(result, node.elems[i], indent, ml,
+            true, newIndent(currIndent, indent, ml))
+      result.nl(ml)
+      result.indent(currIndent)
+      result.add(")")
+    else: result.add("nil")
+  of SCons:
+    if lstArr: result.indent(currIndent)
+    result.add("(")
+    toPretty(result, node.car, indent, ml,
+        true, newIndent(currIndent, indent, ml))
+    result.add(" . ")
+    toPretty(result, node.cdr, indent, ml,
+        true, newIndent(currIndent, indent, ml))
+    result.add(")")
+
+proc pretty*(node: SexpNode, indent = 2): string =
+  ## Converts `node` to its Sexp Representation, with indentation and
+  ## on multiple lines.
+  result = ""
+  toPretty(result, node, indent)
+
+proc `$`*(node: SexpNode): string =
+  ## Converts `node` to its SEXP Representation on one line.
+  result = ""
+  toPretty(result, node, 0, false)
+
+iterator items*(node: SexpNode): SexpNode =
+  ## Iterator for the items of `node`. `node` has to be a SList.
+  assert node.kind == SList
+  for i in items(node.elems):
+    yield i
+
+iterator mitems*(node: var SexpNode): var SexpNode =
+  ## Iterator for the items of `node`. `node` has to be a SList. Items can be
+  ## modified.
+  assert node.kind == SList
+  for i in mitems(node.elems):
+    yield i
+
+proc eat(p: var SexpParser, tok: TTokKind) =
+  if p.tok == tok: discard getTok(p)
+  else: raiseParseErr(p, tokToStr[tok])
+
+proc parseSexp(p: var SexpParser): SexpNode =
+  ## Parses SEXP from a SEXP Parser `p`.
+  case p.tok
+  of tkString:
+    # we capture 'p.a' here, so we need to give it a fresh buffer afterwards:
+    result = newSStringMove(p.a)
+    p.a = ""
+    discard getTok(p)
+  of tkInt:
+    result = newSInt(parseBiggestInt(p.a))
+    discard getTok(p)
+  of tkFloat:
+    result = newSFloat(parseFloat(p.a))
+    discard getTok(p)
+  of tkNil:
+    result = newSNil()
+    discard getTok(p)
+  of tkSymbol:
+    result = newSSymbolMove(p.a)
+    p.a = ""
+    discard getTok(p)
+  of tkParensLe:
+    result = newSList()
+    discard getTok(p)
+    while p.tok notin {tkParensRi, tkDot}:
+      result.add(parseSexp(p))
+      if p.tok != tkSpace: break
+      discard getTok(p)
+    if p.tok == tkDot:
+      eat(p, tkDot)
+      eat(p, tkSpace)
+      result.add(parseSexp(p))
+      result = newSCons(result[0], result[1])
+    eat(p, tkParensRi)
+  of tkSpace, tkDot, tkError, tkParensRi, tkEof:
+    raiseParseErr(p, "(")
+
+proc open*(my: var SexpParser, input: Stream) =
+  ## initializes the parser with an input stream.
+  lexbase.open(my, input)
+  my.kind = sexpError
+  my.a = ""
+
+proc parseSexp*(s: Stream): SexpNode =
+  ## Parses from a buffer `s` into a `SexpNode`.
+  var p: SexpParser
+  p.open(s)
+  discard getTok(p) # read first token
+  result = p.parseSexp()
+  p.close()
+
+proc parseSexp*(buffer: string): SexpNode =
+  ## Parses Sexp from `buffer`.
+  result = parseSexp(newStringStream(buffer))
+
+when isMainModule:
+  let testSexp = parseSexp("""(1 (98 2) nil (2) foobar "foo" 9.234)""")
+  assert(testSexp[0].getNum == 1)
+  assert(testSexp[1][0].getNum == 98)
+  assert(testSexp[2].getElems == @[])
+  assert(testSexp[4].getSymbol == "foobar")
+  assert(testSexp[5].getStr == "foo")
+
+  let alist = parseSexp("""((1 . 2) (2 . "foo"))""")
+  assert(alist[0].getCons.car.getNum == 1)
+  assert(alist[0].getCons.cdr.getNum == 2)
+  assert(alist[1].getCons.cdr.getStr == "foo")
+
+  # Generator:
+  var j = convertSexp([true, false, "foobar", [1, 2, "baz"]])
+  assert($j == """(t nil "foobar" (1 2 "baz"))""")
diff --git a/tools/nimsuggest/tester.nim b/tools/nimsuggest/tester.nim
new file mode 100644
index 000000000..c90afe3db
--- /dev/null
+++ b/tools/nimsuggest/tester.nim
@@ -0,0 +1,182 @@
+# Tester for nimsuggest.
+# Every test file can have a #[!]# comment that is deleted from the input
+# before 'nimsuggest' is invoked to ensure this token doesn't make a
+# crucial difference for Nim's parser.
+
+import os, osproc, strutils, streams, re
+
+type
+  Test = object
+    cmd, dest: string
+    startup: seq[string]
+    script: seq[(string, string)]
+
+const
+  curDir = when defined(windows): "" else: ""
+  DummyEof = "!EOF!"
+
+template tpath(): untyped = getAppDir() / "tests"
+
+proc parseTest(filename: string): Test =
+  const cursorMarker = "#[!]#"
+  let nimsug = curDir & addFileExt("nimsuggest", ExeExt)
+  result.dest = getTempDir() / extractFilename(filename)
+  result.cmd = nimsug & " --tester " & result.dest
+  result.script = @[]
+  result.startup = @[]
+  var tmp = open(result.dest, fmWrite)
+  var specSection = 0
+  var markers = newSeq[string]()
+  var i = 1
+  for x in lines(filename):
+    let marker = x.find(cursorMarker)+1
+    if marker > 0:
+      markers.add "\"" & filename & "\";\"" & result.dest & "\":" & $i & ":" & $marker
+      tmp.writeLine x.replace(cursorMarker, "")
+    else:
+      tmp.writeLine x
+    if x.contains("""""""""):
+      inc specSection
+    elif specSection == 1:
+      if x.startsWith("$nimsuggest"):
+        result.cmd = x % ["nimsuggest", nimsug, "file", filename]
+      elif x.startsWith("!"):
+        if result.cmd.len == 0:
+          result.startup.add x
+        else:
+          result.script.add((x, ""))
+      elif x.startsWith(">"):
+        # since 'markers' here are not complete yet, we do the $substitutions
+        # afterwards
+        result.script.add((x.substr(1).replaceWord("$path", tpath()), ""))
+      elif x.len > 0:
+        # expected output line:
+        let x = x % ["file", filename]
+        result.script[^1][1].add x.replace(";;", "\t") & '\L'
+        # else: ignore empty lines for better readability of the specs
+    inc i
+  tmp.close()
+  # now that we know the markers, substitute them:
+  for a in mitems(result.script):
+    a[0] = a[0] % markers
+
+proc parseCmd(c: string): seq[string] =
+  # we don't support double quotes for now so that
+  # we can later support them properly with escapes and stuff.
+  result = @[]
+  var i = 0
+  var a = ""
+  while true:
+    setLen(a, 0)
+    # eat all delimiting whitespace
+    while c[i] in {' ', '\t', '\l', '\r'}: inc(i)
+    case c[i]
+    of '"': raise newException(ValueError, "double quotes not yet supported: " & c)
+    of '\'':
+      var delim = c[i]
+      inc(i) # skip ' or "
+      while c[i] != '\0' and c[i] != delim:
+        add a, c[i]
+        inc(i)
+      if c[i] != '\0': inc(i)
+    of '\0': break
+    else:
+      while c[i] > ' ':
+        add(a, c[i])
+        inc(i)
+    add(result, a)
+
+proc edit(tmpfile: string; x: seq[string]) =
+  if x.len != 3 and x.len != 4:
+    quit "!edit takes two or three arguments"
+  let f = if x.len >= 4: tpath() / x[3] else: tmpfile
+  try:
+    let content = readFile(f)
+    let newcontent = content.replace(x[1], x[2])
+    if content == newcontent:
+      quit "wrong test case: edit had no effect"
+    writeFile(f, newcontent)
+  except IOError:
+    quit "cannot edit file " & tmpfile
+
+proc exec(x: seq[string]) =
+  if x.len != 2: quit "!exec takes one argument"
+  if execShellCmd(x[1]) != 0:
+    quit "External program failed " & x[1]
+
+proc copy(x: seq[string]) =
+  if x.len != 3: quit "!copy takes two arguments"
+  let rel = tpath()
+  copyFile(rel / x[1], rel / x[2])
+
+proc del(x: seq[string]) =
+  if x.len != 2: quit "!del takes one argument"
+  removeFile(tpath() / x[1])
+
+proc runCmd(cmd, dest: string): bool =
+  result = cmd[0] == '!'
+  if not result: return
+  let x = cmd.parseCmd()
+  case x[0]
+  of "!edit":
+    edit(dest, x)
+  of "!exec":
+    exec(x)
+  of "!copy":
+    copy(x)
+  of "!del":
+    del(x)
+  else:
+    quit "unkown command: " & cmd
+
+proc smartCompare(pattern, x: string): bool =
+  if pattern.contains('*'):
+    result = match(x, re(escapeRe(pattern).replace("\\x2A","(.*)"), {}))
+
+proc runTest(filename: string): int =
+  let s = parseTest filename
+  for cmd in s.startup:
+    if not runCmd(cmd, s.dest):
+      quit "invalid command: " & cmd
+  let cl = parseCmdLine(s.cmd)
+  var p = startProcess(command=cl[0], args=cl[1 .. ^1],
+                       options={poStdErrToStdOut, poUsePath,
+                       poInteractive, poDemon})
+  let outp = p.outputStream
+  let inp = p.inputStream
+  var report = ""
+  var a = newStringOfCap(120)
+  try:
+    # read and ignore anything nimsuggest says at startup:
+    while outp.readLine(a):
+      if a == DummyEof: break
+    for req, resp in items(s.script):
+      if not runCmd(req, s.dest):
+        inp.writeLine(req)
+        inp.flush()
+        var answer = ""
+        while outp.readLine(a):
+          if a == DummyEof: break
+          answer.add a
+          answer.add '\L'
+        if resp != answer and not smartCompare(resp, answer):
+          report.add "\nTest failed: " & filename
+          report.add "\n  Expected:  " & resp
+          report.add "\n  But got:   " & answer
+  finally:
+    inp.writeLine("quit")
+    inp.flush()
+    close(p)
+  if report.len > 0:
+    echo report
+  result = report.len
+
+proc main() =
+  var failures = 0
+  for x in walkFiles(getAppDir() / "tests/t*.nim"):
+    echo "Test ", x
+    failures += runTest(expandFilename(x))
+  if failures > 0:
+    quit 1
+
+main()
diff --git a/tools/nimsuggest/tests/dep_v1.nim b/tools/nimsuggest/tests/dep_v1.nim
new file mode 100644
index 000000000..eae230e85
--- /dev/null
+++ b/tools/nimsuggest/tests/dep_v1.nim
@@ -0,0 +1,8 @@
+
+
+
+
+
+type
+  Foo* = object
+    x*, y*: int
diff --git a/tools/nimsuggest/tests/dep_v2.nim b/tools/nimsuggest/tests/dep_v2.nim
new file mode 100644
index 000000000..ab39721c4
--- /dev/null
+++ b/tools/nimsuggest/tests/dep_v2.nim
@@ -0,0 +1,9 @@
+
+
+
+
+
+type
+  Foo* = object
+    x*, y*: int
+    z*: string
diff --git a/tools/nimsuggest/tests/tdef1.nim b/tools/nimsuggest/tests/tdef1.nim
new file mode 100644
index 000000000..960ffad1c
--- /dev/null
+++ b/tools/nimsuggest/tests/tdef1.nim
@@ -0,0 +1,16 @@
+discard """
+$nimsuggest --tester $file
+>def $1
+def;;skProc;;tdef1.hello;;proc ();;$file;;9;;5;;"";;100
+>def $1
+def;;skProc;;tdef1.hello;;proc ();;$file;;9;;5;;"";;100
+"""
+
+proc hello() string =
+  ## Return hello
+  "Hello"
+
+hel#[!]#lo()
+
+# v uncompleted id for sug (13,2)
+he
diff --git a/tools/nimsuggest/tests/tdot1.nim b/tools/nimsuggest/tests/tdot1.nim
new file mode 100644
index 000000000..bcd44cd84
--- /dev/null
+++ b/tools/nimsuggest/tests/tdot1.nim
@@ -0,0 +1,14 @@
+discard """
+$nimsuggest --tester $file
+>sug $1
+sug;;skField;;x;;int;;$file;;11;;4;;"";;100
+sug;;skField;;y;;int;;$file;;11;;7;;"";;100
+sug;;skProc;;tdot1.main;;proc (f: Foo);;$file;;13;;5;;"";;100
+"""
+
+type
+  Foo = object
+    x, y: int
+
+proc main(f: Foo) =
+  f.#[!]#
diff --git a/tools/nimsuggest/tests/tdot2.nim b/tools/nimsuggest/tests/tdot2.nim
new file mode 100644
index 000000000..a58ac818b
--- /dev/null
+++ b/tools/nimsuggest/tests/tdot2.nim
@@ -0,0 +1,29 @@
+# Test basic editing. We replace the 'false' by 'true' to
+# see whether then the z field is suggested.
+
+const zField = 0i32
+
+type
+  Foo = object
+    x, y: int
+    when zField == 1i32:
+      z: string
+
+proc main(f: Foo) =
+  f.#[!]#
+
+# the tester supports the spec section at the bottom of the file and
+# this way, the line numbers more often stay the same
+discard """
+$nimsuggest --tester $file
+>sug $1
+sug;;skField;;x;;int;;$file;;8;;4;;"";;100
+sug;;skField;;y;;int;;$file;;8;;7;;"";;100
+sug;;skProc;;tdot2.main;;proc (f: Foo);;$file;;12;;5;;"";;100
+!edit 0i32 1i32
+>sug $1
+sug;;skField;;x;;int;;$file;;8;;4;;"";;100
+sug;;skField;;y;;int;;$file;;8;;7;;"";;100
+sug;;skField;;z;;string;;$file;;10;;6;;"";;100
+sug;;skProc;;tdot2.main;;proc (f: Foo);;$file;;12;;5;;"";;100
+"""
diff --git a/tools/nimsuggest/tests/tdot3.nim b/tools/nimsuggest/tests/tdot3.nim
new file mode 100644
index 000000000..5badde867
--- /dev/null
+++ b/tools/nimsuggest/tests/tdot3.nim
@@ -0,0 +1,27 @@
+# Test basic module dependency recompilations.
+
+import dep
+
+proc main(f: Foo) =
+  f.#[!]#
+
+# the tester supports the spec section at the bottom of the file and
+# this way, the line numbers more often stay the same
+
+discard """
+!copy dep_v1.nim dep.nim
+$nimsuggest --tester $file
+>sug $1
+sug;;skField;;x;;int;;*dep.nim;;8;;4;;"";;100
+sug;;skField;;y;;int;;*dep.nim;;8;;8;;"";;100
+sug;;skProc;;tdot3.main;;proc (f: Foo);;$file;;5;;5;;"";;100
+
+!copy dep_v2.nim dep.nim
+>mod $path/dep.nim
+>sug $1
+sug;;skField;;x;;int;;*dep.nim;;8;;4;;"";;100
+sug;;skField;;y;;int;;*dep.nim;;8;;8;;"";;100
+sug;;skField;;z;;string;;*dep.nim;;9;;4;;"";;100
+sug;;skProc;;tdot3.main;;proc (f: Foo);;$file;;5;;5;;"";;100
+!del dep.nim
+"""
diff --git a/tools/nimsuggest/tests/tinclude.nim b/tools/nimsuggest/tests/tinclude.nim
new file mode 100644
index 000000000..77492d745
--- /dev/null
+++ b/tools/nimsuggest/tests/tinclude.nim
@@ -0,0 +1,7 @@
+discard """
+$nimsuggest --tester compiler/nim.nim
+>def compiler/semexprs.nim:13:50
+def;;skType;;ast.PSym;;PSym;;*ast.nim;;668;;2;;"";;100
+>def compiler/semexprs.nim:13:50
+def;;skType;;ast.PSym;;PSym;;*ast.nim;;668;;2;;"";;100
+"""
diff --git a/tools/nimsuggest/tests/tstrutils.nim b/tools/nimsuggest/tests/tstrutils.nim
new file mode 100644
index 000000000..f5cda9505
--- /dev/null
+++ b/tools/nimsuggest/tests/tstrutils.nim
@@ -0,0 +1,9 @@
+discard """
+$nimsuggest --tester lib/pure/strutils.nim
+>def lib/pure/strutils.nim:2300:6
+def;;skTemplate;;system.doAssert;;proc (cond: bool, msg: string): typed;;*/lib/system.nim;;*;;9;;"";;100
+"""
+
+# Line 2300 in strutils.nim is doAssert and this is unlikely to change
+# soon since there are a whole lot of doAsserts there.
+
diff --git a/tools/nimweb.nim b/tools/nimweb.nim
index 65af67216..9a21a0fb5 100644
--- a/tools/nimweb.nim
+++ b/tools/nimweb.nim
@@ -29,7 +29,7 @@ type
   TRssItem = object
     year, month, day, title, url, content: string
   TAction = enum
-    actAll, actOnlyWebsite, actPdf, actJson2
+    actAll, actOnlyWebsite, actPdf, actJson2, actOnlyDocs
 
   Sponsor = object
     logo: string
@@ -72,7 +72,7 @@ include "website.tmpl"
 # ------------------------- configuration file -------------------------------
 
 const
-  version = "0.7"
+  version = "0.8"
   usage = "nimweb - Nim Website Generator Version " & version & """
 
   (c) 2015 Andreas Rumpf
@@ -85,11 +85,13 @@ Options:
   -v, --version       shows the version
   --website           only build the website, not the full documentation
   --pdf               build the PDF version of the documentation
+  --json2             build JSON of the documentation
+  --onlyDocs          build only the documentation
 Compile_options:
   will be passed to the Nim compiler
 """
 
-  rYearMonthDay = r"(\d{4})_(\d{2})_(\d{2})"
+  rYearMonthDay = r"on\s+(\d{2})\/(\d{2})\/(\d{4})"
   rssUrl = "http://nim-lang.org/news.xml"
   rssNewsUrl = "http://nim-lang.org/news.html"
   activeSponsors = "web/sponsors.csv"
@@ -157,6 +159,7 @@ proc parseCmdLine(c: var TConfigData) =
       of "website": action = actOnlyWebsite
       of "pdf": action = actPdf
       of "json2": action = actJson2
+      of "onlydocs": action = actOnlyDocs
       of "googleanalytics":
         c.gaId = val
         c.nimArgs.add("--doc.googleAnalytics:" & val & " ")
@@ -315,6 +318,7 @@ proc buildDoc(c: var TConfigData, destPath: string) =
   exec(findNim() & " buildIndex -o:$1/theindex.html $1" % [destPath])
 
 proc buildPdfDoc(c: var TConfigData, destPath: string) =
+  createDir(destPath)
   if os.execShellCmd("pdflatex -version") != 0:
     echo "pdflatex not found; no PDF documentation generated"
   else:
@@ -347,6 +351,7 @@ proc buildAddDoc(c: var TConfigData, destPath: string) =
 proc parseNewsTitles(inputFilename: string): seq[TRssItem] =
   # Goes through each news file, returns its date/title.
   result = @[]
+  var matches: array[3, string]
   let reYearMonthDay = re(rYearMonthDay)
   for kind, path in walkDir(inputFilename):
     let (dir, name, ext) = path.splitFile
@@ -354,8 +359,8 @@ proc parseNewsTitles(inputFilename: string): seq[TRssItem] =
       let content = readFile(path)
       let title = content.splitLines()[0]
       let urlPath = "news/" & name & ".html"
-      if name =~ reYearMonthDay:
-        result.add(TRssItem(year: matches[0], month: matches[1], day: matches[2],
+      if content.find(reYearMonthDay, matches) >= 0:
+        result.add(TRssItem(year: matches[2], month: matches[1], day: matches[0],
           title: title, url: "http://nim-lang.org/" & urlPath,
           content: content))
   result.reverse()
@@ -501,11 +506,19 @@ proc buildWebsite(c: var TConfigData) =
 proc main(c: var TConfigData) =
   buildWebsite(c)
   buildJS("web/upload")
-  buildAddDoc(c, "web/upload")
-  buildDocSamples(c, "web/upload")
-  buildDoc(c, "web/upload")
-  buildDocSamples(c, "doc")
-  buildDoc(c, "doc")
+  const docup = "web/upload/" & NimVersion
+  createDir(docup)
+  buildAddDoc(c, docup)
+  buildDocSamples(c, docup)
+  buildDoc(c, docup)
+  createDir("doc/html")
+  buildDocSamples(c, "doc/html")
+  buildDoc(c, "doc/html")
+
+proc onlyDocs(c: var TConfigData) =
+  createDir("doc/html")
+  buildDocSamples(c, "doc/html")
+  buildDoc(c, "doc/html")
 
 proc json2(c: var TConfigData) =
   const destPath = "web/json2"
@@ -526,6 +539,7 @@ parseCmdLine(c)
 parseIniFile(c)
 case action
 of actOnlyWebsite: buildWebsite(c)
-of actPdf: buildPdfDoc(c, "doc")
+of actPdf: buildPdfDoc(c, "doc/pdf")
+of actOnlyDocs: onlyDocs(c)
 of actAll: main(c)
 of actJson2: json2(c)
diff --git a/tools/noprefix.nim b/tools/noprefix.nim
deleted file mode 100644
index d16c2b520..000000000
--- a/tools/noprefix.nim
+++ /dev/null
@@ -1,32 +0,0 @@
-# strip those silly GTK/ATK prefixes...
-
-import
-  expandimportc, os
-
-const
-  filelist = [
-    ("sdl/sdl", "sdl"),
-    ("sdl/sdl_net", "sdl"),
-    ("sdl/sdl_gfx", "sdl"),
-    ("sdl/sdl_image", "sdl"),
-    ("sdl/sdl_mixer_nosmpeg", "sdl"),
-    ("sdl/sdl_mixer", "sdl"),
-    ("sdl/sdl_ttf", "sdl"),
-    ("sdl/smpeg", "sdl"),
-
-    ("libcurl", "curl"),
-    ("mysql", "mysql"),
-    ("postgres", ""),
-    ("sqlite3", "sqlite3"),
-
-    ("pcre/pcre", "pcre")
-  ]
-
-proc createDirs =
-  createDir("lib/newwrap/sdl")
-  createDir("lib/newwrap/pcre")
-
-for filename, prefix in items(filelist):
-  var f = addFileExt(filename, "nim")
-  main("lib/wrappers" / f, "lib/newwrap" / f, prefix)
-
diff --git a/start.bat b/tools/start.bat
index 685932c47..a4475fac7 100644
--- a/start.bat
+++ b/tools/start.bat
@@ -1,6 +1,8 @@
 @echo off

 REM COLOR 0A

-SET NIMPATH=%~dp0

+SET NIMPATH=%~dp0\..

 SET PATH=%NIMPATH%\bin;%NIMPATH%\dist\mingw\bin;%PATH%

-cmd

+cd %NIMPATH%

+cmd 

+

 

diff --git a/tools/vccenv/vccenv.nim b/tools/vccenv/vccenv.nim
new file mode 100644
index 000000000..a335efd10
--- /dev/null
+++ b/tools/vccenv/vccenv.nim
@@ -0,0 +1,58 @@
+import strtabs, os, osproc, streams, strutils
+
+const
+  comSpecEnvKey = "ComSpec"
+  vsComnToolsEnvKeys = [
+    "VS140COMNTOOLS",
+    "VS130COMNTOOLS",
+    "VS120COMNTOOLS",
+    "VS110COMNTOOLS",
+    "VS100COMNTOOLS",
+    "VS90COMNTOOLS"
+  ]
+  vcvarsallRelativePath = joinPath("..", "..", "VC", "vcvarsall")
+
+proc getVsComnToolsPath*(): TaintedString =
+  for vsComnToolsEnvKey in vsComnToolsEnvKeys:
+    let vsComnToolsEnvVal = getEnv vsComnToolsEnvKey
+    if vsComnToolsEnvVal.len > 0:
+      return vsComnToolsEnvVal
+
+proc getVccEnv*(platform: string, windowsStoreSdk: bool = false,
+                sdkVersion: string = nil): StringTableRef =
+  var comSpecCommandString = getEnv comSpecEnvKey
+  if comSpecCommandString.len == 0:
+    comSpecCommandString = "cmd"
+
+  let vsComnToolsPath = getVsComnToolsPath()
+  if vsComnToolsPath.len < 1:
+    return nil
+  let vcvarsallPath = expandFilename joinPath(vsComnToolsPath, vcvarsallRelativePath)
+
+  var vcvarsallArgs: seq[string] = @[]
+  if platform.len > 0:
+    vcvarsallArgs.add(platform)
+  if windowsStoreSdk:
+    vcvarsallArgs.add("store")
+  if sdkVersion.len > 0:
+    vcvarsallArgs.add(sdkVersion)
+  let vcvarsallArgString = vcvarsallArgs.join(" ")
+
+  var vcvarsallCommandString: string
+  if vcvarsallArgString.len > 0:
+    vcvarsallCommandString = "\"$1\" $2" % [vcvarsallPath, vcvarsallArgString]
+  else:
+    vcvarsallCommandString = vcvarsallPath
+
+  let vcvarsallExecCommand = "\"$1\" /C \"$2 && SET\"" %
+                             [comSpecCommandString, vcvarsallCommandString]
+  when defined(release):
+    let vccvarsallOptions = {poEvalCommand, poDemon}
+  else:
+    let vccvarsallOptions = {poEchoCmd, poEvalCommand, poDemon}
+  let vcvarsallStdOut = execProcess(vcvarsallExecCommand, options = vccvarsallOptions)
+  result = newStringTable(modeCaseInsensitive)
+  for line in vcvarsallStdOut.splitLines:
+    let idx = line.find('=')
+    if idx > 0:
+      result[line[0..(idx - 1)]] = line[(idx + 1)..(line.len - 1)]
diff --git a/tools/vccenv/vccexe.nim b/tools/vccenv/vccexe.nim
new file mode 100644
index 000000000..892246830
--- /dev/null
+++ b/tools/vccenv/vccexe.nim
@@ -0,0 +1,66 @@
+import strutils, strtabs, os, osproc, vccenv
+
+when defined(release):
+  let vccOptions = {poParentStreams}
+else:
+  let vccOptions = {poEchoCmd, poParentStreams}
+
+const 
+  platformPrefix = "--platform"
+  winstorePrefix = "--winstore"
+  sdkversionPrefix = "--sdkversion"
+
+  platformSepIdx = platformPrefix.len
+  sdkversionSepIdx = sdkversionPrefix.len
+  
+  HelpText = """
++-----------------------------------------------------------------+
+|         Microsoft C/C++ compiler wrapper for Nim                |
+|             (c) 2016 Fredrik Høisæther Rasch                    |
++-----------------------------------------------------------------+
+
+Usage:
+  vccexe [options] [compileroptions]
+Options:
+  --platform:<arch>   Specify the Compiler Platform Tools architecture
+                      <arch>: x86 | amd64 | arm | x86_amd64 | x86_arm | amd64_x86 | amd64_arm
+  --winstore          Use Windows Store (rather than desktop) development tools
+  --sdkversion:<v>    Use a specific Windows SDK version:
+                      <v> is either the full Windows 10 SDK version number or 
+                      "8.1" to use the windows 8.1 SDK
+
+Other command line arguments are passed on to the
+Microsoft C/C++ compiler for the specified SDK toolset
+"""
+
+when isMainModule:
+  var platformArg: string = nil
+  var sdkVersionArg: string = nil
+  var storeArg: bool = false
+
+  var clArgs: seq[TaintedString] = @[]
+
+  var wrapperArgs = commandLineParams()
+  for wargv in wrapperArgs:
+    # Check whether the current argument contains -- prefix
+    if wargv.startsWith(platformPrefix): # Check for platform
+      platformArg = wargv.substr(platformSepIdx + 1)
+    elif wargv == winstorePrefix: # Check for winstore
+      storeArg = true
+    elif wargv.startsWith(sdkversionPrefix): # Check for sdkversion
+      sdkVersionArg = wargv.substr(sdkversionSepIdx + 1)
+    else: # Regular cl.exe argument -> store for final cl.exe invocation
+      if (wargv.len == 2) and (wargv[1] == '?'):
+        echo HelpText
+      clArgs.add(wargv)
+
+  var vccEnvStrTab = getVccEnv(platformArg, storeArg, sdkVersionArg)  
+  if vccEnvStrTab != nil:
+    for vccEnvKey, vccEnvVal in vccEnvStrTab:
+      putEnv(vccEnvKey, vccEnvVal)
+  let vccProcess = startProcess(
+      "cl.exe",
+      args = clArgs,
+      options = vccOptions
+    )
+  quit vccProcess.waitForExit()
diff --git a/tools/website.tmpl b/tools/website.tmpl
index 2801fea96..9aa64310d 100644
--- a/tools/website.tmpl
+++ b/tools/website.tmpl
@@ -61,13 +61,13 @@
         <div id="slideshow">
           <!-- slides -->
           <div id="slide0" class="active niaslide">
-            <a href="sponsors.html">
-              <img src="${rootDir}assets/bountysource/meet_sponsors.png" alt="Meet our BountySource sponsors!"/>
+            <a href="news/e030_nim_in_action_in_production.html">
+              <img src="${rootDir}assets/niminaction/banner2.png" alt="A printed copy of Nim in Action should be available in March 2017!"/>
             </a>
           </div>
           <div id="slide1" class="niaslide">
-            <a href="news/2016_01_27_nim_in_action_is_now_available.html">
-              <img src="${rootDir}assets/niminaction/banner.jpg" alt="New in Manning Early Access Program: Nim in Action!"/>
+            <a href="sponsors.html">
+              <img src="${rootDir}assets/bountysource/meet_sponsors.png" alt="Meet our BountySource sponsors!"/>
             </a>
           </div>
           <div id="slide2" class="codeslide2">
diff --git a/web/assets/niminaction/banner2.png b/web/assets/niminaction/banner2.png
new file mode 100644
index 000000000..3cabd195d
--- /dev/null
+++ b/web/assets/niminaction/banner2.png
Binary files differdiff --git a/web/download.rst b/web/download.rst
index cf0841577..c388dd132 100644
--- a/web/download.rst
+++ b/web/download.rst
@@ -3,17 +3,34 @@ Download the compiler
 
 You can download the latest version of the Nim compiler here.
 
-Binaries
---------
+Windows
+-------
 
-Right now binaries are only provided for Windows. You can download
-an installer for both 32 bit and 64 bit versions of Windows below. These
+Zips
+%%%%
+
+We now encourage you to install via the provided zipfiles:
+
+* | 32 bit: `nim-0.15.2_x32.zip <download/nim-0.15.2_x32.zip>`_
+  | SHA-256  0f1bfb74751f55e090140a361c08e9f39f1dd03f1f0c070c061f2d5049ab9f96
+* | 64 bit: `nim-0.15.2_x64.zip <download/nim-0.15.2_x64.zip>`_
+  | SHA-256  ceea42de6ebcd41032ee51f04526dc4cf2cbb0958ca6ad2321cf21944e05f553
+
+Unzip these where you want and optionally run ``finish.exe`` to
+detect your MingW environment.
+
+Exes
+%%%%
+
+You can download an installer for both 32 bit and 64 bit versions of
+Windows below. Note that these installers have some known issues and
+so will unlikely to be provided further in the future. These
 installers have everything you need to use Nim, including a C compiler.
 
-* | 32 bit: `nim-0.15.0_x32.exe <download/nim-0.15.0_x32.exe>`_
-  | SHA-256  0ca8931e3369735bbafdf93de98a8fd0f425870f1173845e7601922a5e00c3c2
-* | 64 bit: `nim-0.15.0_x64.exe <download/nim-0.15.0_x64.exe>`_
-  | SHA-256  7bb9321cd9fb2860d36ee9b248e0202d7d4e36e2272a2f128fbce96fd4a9bfd6
+* | 32 bit: `nim-0.15.2_x32.exe <download/nim-0.15.2_x32.exe>`_
+  | SHA-256  8d648295dbd59cb315c98926a1da9f1f68773a1a2ef3d9d4c91c59387167efa3
+* | 64 bit: `nim-0.15.2_x64.exe <download/nim-0.15.2_x64.exe>`_
+  | SHA-256  8c7efc6571921c2d2e5e995f801d4229ea1de19fbdabdcba1628307bd4612392
 
 These installers also include Aporia, Nimble and other useful Nim tools to get
 you started with Nim development!
@@ -31,8 +48,8 @@ like systems.
 
 Firstly, download this archive:
 
-* | `nim-0.15.0.tar.xz (4.5MB) <download/nim-0.15.0.tar.xz>`_
-  | SHA-256  c514535050b2b2156147bbe6e23aafe07cd996b2afa2c81fa9a09e1cd8c669fb
+* | `nim-0.15.2.tar.xz (4.5MB) <download/nim-0.15.2.tar.xz>`_
+  | SHA-256  905df2316262aa2cbacae067acf45fc05c2a71c8c6fde1f2a70c927ebafcfe8a
 
 Extract the archive. Then copy the extracted files into your chosen installation
 directory, ideally somewhere in your home directory.
@@ -44,7 +61,7 @@ Now open a terminal and follow these instructions:
 ``cd ~/programs/nim``.
 * run ``sh build.sh``.
 * Add ``$your_install_dir/bin`` to your PATH.
-* To build associated tools like ``nimble`` and ``nimsuggest`` run ``nim e install_tools.nims``.
+* To build associated tools like ``nimble`` and ``nimsuggest`` run ``nim c koch && koch tools``.
 
 After restarting your terminal, you should be able to run ``nim -v``
 which should show you the version of Nim you just installed.
diff --git a/web/news.rst b/web/news.rst
index f819c384c..e8d97b69e 100644
--- a/web/news.rst
+++ b/web/news.rst
@@ -2,102 +2,108 @@
 News
 ====
 
-`2016-09-30 Nim Version 0.15.0 released <news/2016_09_00_version_0_15_0_released.html>`_
+`2016-11-20 Nim in Action is going into production! <news/e030_nim_in_action_in_production.html>`_
 ===================================
 
-`2016-09-03 Nim Community Survey results <news/2016_09_03_nim_community_survey_results.html>`_
+`2016-10-23 Nim Version 0.15.2 released <news/e028_version_0_15_2.html>`_
 ===================================
 
-`2016-08-06 BountySource Update: The Road to v1.0 <news/2016_08_06_bountysource_update_the_road_to_v10.html>`_
+`2016-09-30 Nim Version 0.15.0 released <news/e027_version_0_15_0.html>`_
 ===================================
 
-`2016-06-23 Launching the 2016 Nim community survey <news/2016_06_23_launching_the_2016_nim_community_survey.html>`_
+`2016-09-03 Nim Community Survey results <news/e026_survey_results.html>`_
 ===================================
 
-`2016-06-11 Version 0.14.2 released <news/2016_06_11_version_0_14_2_released.html>`_
+`2016-08-06 BountySource Update: The Road to v1.0 <news/e025_bountysource_update.html>`_
 ===================================
 
-`2016-06-07 Version 0.14.0 released <news/2016_06_07_version_0_14_0_released.html>`_
+`2016-06-23 Launching the 2016 Nim community survey <news/e024_survey.html>`_
 ===================================
 
-`2016-06-04 Meet our BountySource sponsors <news/2016_06_04_meet_our_bountysource_sponsors.html>`_
+`2016-06-11 Version 0.14.2 released <news/e023_version_0_14_2.html>`_
 ===================================
 
-`2016-01-27 Nim in Action is now available! <news/2016_01_27_nim_in_action_is_now_available.html>`_
+`2016-06-07 Version 0.14.0 released <news/e022_version_0_14_0.html>`_
+===================================
+
+`2016-06-04 Meet our BountySource sponsors <news/e021_meet_sponsors.html>`_
+===================================
+
+`2016-01-27 Nim in Action is now available! <news/e020_nim_in_action.html>`_
 ==================================
 
-`2016-01-18 Version 0.13.0 released <news/2016_01_18_version_0_13_0_released.html>`_
+`2016-01-18 Version 0.13.0 released <news/e019_version_0_13_0.html>`_
 ==================================
 
-`2016-01-18 Andreas Rumpf's talk at OSCON Amsterdam <news/2016_01_18_andreas_rumpfs_talk_at_oscon_amsterdam.html>`_
+`2016-01-18 Andreas Rumpf's talk at OSCON Amsterdam <news/e018_oscon_amsterdam.html>`_
 ==================================================
 
-`2015-10-27 Version 0.12.0 released <news/2015_10_27_version_0_12_0_released.html>`_
+`2015-10-27 Version 0.12.0 released <news/e017_version_0_12_0.html>`_
 ==================================
 
-`2015-10-16 First Nim conference <news/2015_10_16_first_nim_conference.html>`_
+`2015-10-16 First Nim conference <news/e016_nim_conf1.html>`_
 ===============================
 
-`2015-05-04 Version 0.11.2 released <news/2015_05_04_version_0_11_2_released.html>`_
+`2015-05-04 Version 0.11.2 released <news/e015_version_0_11_2.html>`_
 ==================================
 
-`2015-04-30 Version 0.11.0 released <news/2015_04_30_version_0_11_0_released.html>`_
+`2015-04-30 Version 0.11.0 released <news/e014_version_0_11_0.html>`_
 ==================================
 
-`2014-12-29 Version 0.10.2 released <news/2014_12_29_version_0_10_2_released.html>`_
+`2014-12-29 Version 0.10.2 released <news/e013_version_0_10_2.html>`_
 ==================================
 
 
-`2014-10-19 Version 0.9.6 released <news/2014_10_19_version_0_9_6_released.html>`_
+`2014-10-19 Version 0.9.6 released <news/e012_version_0_9_6.html>`_
 =================================
 
 
-`2014-04-21 Version 0.9.4 released <news/2014_04_21_version_0_9_4_released.html>`_
+`2014-04-21 Version 0.9.4 released <news/e011_version_0_9_4.html>`_
 =================================
 
 
-`2014-02-11 Nimrod Featured in Dr. Dobb's Journal <news/2014_02_11_nimrod_featured_in_dr_dobbs_journal.html>`_
+`2014-02-11 Nimrod Featured in Dr. Dobb's Journal <news/e010_dr_dobbs_journal.html>`_
 ================================================
 
 
-`2014-01-15 Andreas Rumpf's talk on Nimrod at Strange Loop 2013 is now online <news/2014_01_15_andreas_rumpfs_talk_on_nimrod.html>`_
+`2014-01-15 Andreas Rumpf's talk on Nimrod at Strange Loop 2013 is now online <news/e009_andreas_rumpfs_talk.html>`_
 ============================================================================
 
 
-`2013-05-20 New website design! <news/2013_05_20_new_website_design.html>`_
+`2013-05-20 New website design! <news/e008_new_website.html>`_
 ==============================
 
 
 
-`2013-05-20 Version 0.9.2 released <news/2013_05_20_version_0_9_2_released.html>`_
+`2013-05-20 Version 0.9.2 released <news/e007_version_0_9_2.html>`_
 =================================
 
 
 
-`2012-09-23 Version 0.9.0 released <news/2012_09_23_version_0_9_0_released.html>`_
+`2012-09-23 Version 0.9.0 released <news/e006_version_0_9_0.html>`_
 =================================
 
 
 
-`2012-02-09 Version 0.8.14 released <news/2012_02_09_version_0_8_14_released.html>`_
+`2012-02-09 Version 0.8.14 released <news/e005_version_0_8_14.html>`_
 ==================================
 
 
 
-`2011-07-10 Version 0.8.12 released <news/2011_07_10_version_0_8_12_released.html>`_
+`2011-07-10 Version 0.8.12 released <news/e004_version_0_8_12.html>`_
 ==================================
 
 
-`2010-10-20 Version 0.8.10 released <news/2010_10_20_version_0_8_10_released.html>`_
+`2010-10-20 Version 0.8.10 released <news/e003_version_0_8_10.html>`_
 ==================================
 
 
 
-`2010-03-14 Version 0.8.8 released <news/2010_03_14_version_0_8_8_released.html>`_
+`2010-03-14 Version 0.8.8 released <news/e002_version_0_8_8.html>`_
 =================================
 
 
-`2009-12-21 Version 0.8.6 released <news/2009_12_21_version_0_8_6_released.html>`_
+`2009-12-21 Version 0.8.6 released <news/e001_version_0_8_6.html>`_
 =================================
 
 
diff --git a/web/news/2009_12_21_version_0_8_6_released.rst b/web/news/e001_version_0_8_6.rst
index 019168a44..019168a44 100644
--- a/web/news/2009_12_21_version_0_8_6_released.rst
+++ b/web/news/e001_version_0_8_6.rst
diff --git a/web/news/2010_03_14_version_0_8_8_released.rst b/web/news/e002_version_0_8_8.rst
index 2df476814..2df476814 100644
--- a/web/news/2010_03_14_version_0_8_8_released.rst
+++ b/web/news/e002_version_0_8_8.rst
diff --git a/web/news/2010_10_20_version_0_8_10_released.rst b/web/news/e003_version_0_8_10.rst
index f72c0076c..f72c0076c 100644
--- a/web/news/2010_10_20_version_0_8_10_released.rst
+++ b/web/news/e003_version_0_8_10.rst
diff --git a/web/news/2011_07_10_version_0_8_12_released.rst b/web/news/e004_version_0_8_12.rst
index 5f154b2db..5f154b2db 100644
--- a/web/news/2011_07_10_version_0_8_12_released.rst
+++ b/web/news/e004_version_0_8_12.rst
diff --git a/web/news/2012_02_09_version_0_8_14_released.rst b/web/news/e005_version_0_8_14.rst
index 4050c8b93..4050c8b93 100644
--- a/web/news/2012_02_09_version_0_8_14_released.rst
+++ b/web/news/e005_version_0_8_14.rst
diff --git a/web/news/2012_09_23_version_0_9_0_released.rst b/web/news/e006_version_0_9_0.rst
index 5635ca94c..5635ca94c 100644
--- a/web/news/2012_09_23_version_0_9_0_released.rst
+++ b/web/news/e006_version_0_9_0.rst
diff --git a/web/news/2013_05_20_version_0_9_2_released.rst b/web/news/e007_version_0_9_2.rst
index 89352c06c..89352c06c 100644
--- a/web/news/2013_05_20_version_0_9_2_released.rst
+++ b/web/news/e007_version_0_9_2.rst
diff --git a/web/news/2013_05_20_new_website_design.rst b/web/news/e008_new_website.rst
index b36cc99dd..b36cc99dd 100644
--- a/web/news/2013_05_20_new_website_design.rst
+++ b/web/news/e008_new_website.rst
diff --git a/web/news/2014_01_15_andreas_rumpfs_talk_on_nimrod.rst b/web/news/e009_andreas_rumpfs_talk.rst
index 00cc5e101..00cc5e101 100644
--- a/web/news/2014_01_15_andreas_rumpfs_talk_on_nimrod.rst
+++ b/web/news/e009_andreas_rumpfs_talk.rst
diff --git a/web/news/2014_02_11_nimrod_featured_in_dr_dobbs_journal.rst b/web/news/e010_dr_dobbs_journal.rst
index b48ccf31f..b48ccf31f 100644
--- a/web/news/2014_02_11_nimrod_featured_in_dr_dobbs_journal.rst
+++ b/web/news/e010_dr_dobbs_journal.rst
diff --git a/web/news/2014_04_21_version_0_9_4_released.rst b/web/news/e011_version_0_9_4.rst
index 2714c5c78..2714c5c78 100644
--- a/web/news/2014_04_21_version_0_9_4_released.rst
+++ b/web/news/e011_version_0_9_4.rst
diff --git a/web/news/2014_10_19_version_0_9_6_released.rst b/web/news/e012_version_0_9_6.rst
index 7a148aaa5..7a148aaa5 100644
--- a/web/news/2014_10_19_version_0_9_6_released.rst
+++ b/web/news/e012_version_0_9_6.rst
diff --git a/web/news/2014_12_29_version_0_10_2_released.rst b/web/news/e013_version_0_10_2.rst
index ad8afa3bf..ad8afa3bf 100644
--- a/web/news/2014_12_29_version_0_10_2_released.rst
+++ b/web/news/e013_version_0_10_2.rst
diff --git a/web/news/2015_04_30_version_0_11_0_released.rst b/web/news/e014_version_0_11_0.rst
index a8a58f2ae..a8a58f2ae 100644
--- a/web/news/2015_04_30_version_0_11_0_released.rst
+++ b/web/news/e014_version_0_11_0.rst
diff --git a/web/news/2015_05_04_version_0_11_2_released.rst b/web/news/e015_version_0_11_2.rst
index 273182340..273182340 100644
--- a/web/news/2015_05_04_version_0_11_2_released.rst
+++ b/web/news/e015_version_0_11_2.rst
diff --git a/web/news/2015_10_16_first_nim_conference.rst b/web/news/e016_nim_conf1.rst
index 228bffd28..228bffd28 100644
--- a/web/news/2015_10_16_first_nim_conference.rst
+++ b/web/news/e016_nim_conf1.rst
diff --git a/web/news/2015_10_27_version_0_12_0_released.rst b/web/news/e017_version_0_12_0.rst
index 63088f9e2..63088f9e2 100644
--- a/web/news/2015_10_27_version_0_12_0_released.rst
+++ b/web/news/e017_version_0_12_0.rst
diff --git a/web/news/2016_01_18_andreas_rumpfs_talk_at_oscon_amsterdam.rst b/web/news/e018_oscon_amsterdam.rst
index fcb4a8794..fcb4a8794 100644
--- a/web/news/2016_01_18_andreas_rumpfs_talk_at_oscon_amsterdam.rst
+++ b/web/news/e018_oscon_amsterdam.rst
diff --git a/web/news/2016_01_18_version_0_13_0_released.rst b/web/news/e019_version_0_13_0.rst
index 2c8e66fa3..2c8e66fa3 100644
--- a/web/news/2016_01_18_version_0_13_0_released.rst
+++ b/web/news/e019_version_0_13_0.rst
diff --git a/web/news/2016_01_27_nim_in_action_is_now_available.rst b/web/news/e020_nim_in_action.rst
index 33bcb7947..33bcb7947 100644
--- a/web/news/2016_01_27_nim_in_action_is_now_available.rst
+++ b/web/news/e020_nim_in_action.rst
diff --git a/web/news/2016_06_04_meet_our_bountysource_sponsors.rst b/web/news/e021_meet_sponsors.rst
index 0bfb472c5..0bfb472c5 100644
--- a/web/news/2016_06_04_meet_our_bountysource_sponsors.rst
+++ b/web/news/e021_meet_sponsors.rst
diff --git a/web/news/2016_06_07_version_0_14_0_released.rst b/web/news/e022_version_0_14_0.rst
index 6634d0053..6634d0053 100644
--- a/web/news/2016_06_07_version_0_14_0_released.rst
+++ b/web/news/e022_version_0_14_0.rst
diff --git a/web/news/2016_06_11_version_0_14_2_released.rst b/web/news/e023_version_0_14_2.rst
index cbfe52713..cbfe52713 100644
--- a/web/news/2016_06_11_version_0_14_2_released.rst
+++ b/web/news/e023_version_0_14_2.rst
diff --git a/web/news/2016_06_23_launching_the_2016_nim_community_survey.rst b/web/news/e024_survey.rst
index 0b87577aa..0b87577aa 100644
--- a/web/news/2016_06_23_launching_the_2016_nim_community_survey.rst
+++ b/web/news/e024_survey.rst
diff --git a/web/news/2016_08_06_bountysource_update_the_road_to_v10.rst b/web/news/e025_bountysource_update.rst
index 00ca7022e..00ca7022e 100644
--- a/web/news/2016_08_06_bountysource_update_the_road_to_v10.rst
+++ b/web/news/e025_bountysource_update.rst
diff --git a/web/news/2016_09_03_nim_community_survey_results.rst b/web/news/e026_survey_results.rst
index 106dce0e4..106dce0e4 100644
--- a/web/news/2016_09_03_nim_community_survey_results.rst
+++ b/web/news/e026_survey_results.rst
diff --git a/web/news/2016_09_30_version_0_15_0_released.rst b/web/news/e027_version_0_15_0.rst
index 47c02e9e4..47c02e9e4 100644
--- a/web/news/2016_09_30_version_0_15_0_released.rst
+++ b/web/news/e027_version_0_15_0.rst
diff --git a/web/news/e028_version_0_15_2.rst b/web/news/e028_version_0_15_2.rst
new file mode 100644
index 000000000..601a26646
--- /dev/null
+++ b/web/news/e028_version_0_15_2.rst
@@ -0,0 +1,77 @@
+Version 0.15.2 released
+=======================
+
+.. container:: metadata
+
+  Posted by Andreas Rumpf on 23/10/2016
+
+We're happy to announce that the latest release of Nim, version 0.15.2, is now
+available!
+
+As always, you can grab the latest version from the
+`downloads page <http://nim-lang.org/download.html>`_.
+
+This release is a pure bugfix release fixing the most pressing issues and
+regressions of 0.15.0. For Windows we now provide zipfiles in addition to the
+NSIS based installer which proves to be hard to maintain and after all these
+months still has serious issues. So we encourage you download the .zip
+file instead of the .exe file! Unzip it somewhere, run ``finish.exe`` to
+detect your MingW installation, done. ``finish.exe`` can also set your PATH
+environment variable.
+
+
+Bugfixes
+--------
+
+The list below has been generated based on the commits in Nim's git
+repository. As such it lists only the issues which have been closed
+via a commit, for a full list see
+`this link on Github <https://github.com/nim-lang/Nim/issues?utf8=%E2%9C%93&q=is%3Aissue+closed%3A%222016-09-30+..+2016-10-23%22+>`_.
+
+
+- Fixed "`NimMain` not exported in DLL, but `NimMainInner` is"
+  (`#4840 <https://github.com/nim-lang/Nim/issues/4840>`_)
+- Fixed "Tables clear seems to be broken"
+  (`#4844 <https://github.com/nim-lang/Nim/issues/4844>`_)
+- Fixed "compiler: internal error"
+  (`#4845 <https://github.com/nim-lang/Nim/issues/4845>`_)
+- Fixed "trivial macro breaks type checking in the compiler"
+  (`#4608 <https://github.com/nim-lang/Nim/issues/4608>`_)
+- Fixed "derived generic types with static[T] breaks type checking in v0.15.0 (worked in v0.14.2)"
+  (`#4863 <https://github.com/nim-lang/Nim/issues/4863>`_)
+- Fixed "xmlparser.parseXml is not recognised as GC-safe"
+  (`#4899 <https://github.com/nim-lang/Nim/issues/4899>`_)
+- Fixed "async makes generics instantiate only once"
+  (`#4856 <https://github.com/nim-lang/Nim/issues/4856>`_)
+- Fixed "db_common docs aren't generated"
+  (`#4895 <https://github.com/nim-lang/Nim/issues/4895>`_)
+- Fixed "rdstdin  disappeared from documentation index"
+  (`#3755 <https://github.com/nim-lang/Nim/issues/3755>`_)
+- Fixed "ICE on template call resolution"
+  (`#4875 <https://github.com/nim-lang/Nim/issues/4875>`_)
+- Fixed "Invisible code-block"
+  (`#3078 <https://github.com/nim-lang/Nim/issues/3078>`_)
+- Fixed "nim doc does not generate doc comments correctly"
+  (`#4913 <https://github.com/nim-lang/Nim/issues/4913>`_)
+- Fixed "nim doc2 fails on ARM when running against lib/pure/coro.nim"
+  (`#4879 <https://github.com/nim-lang/Nim/issues/4879>`_)
+- Fixed "xmlparser does not unescape correctly"
+  (`#1518 <https://github.com/nim-lang/Nim/issues/1518>`_)
+- Fixed "[docs] mysterious "raise hook""
+  (`#3485 <https://github.com/nim-lang/Nim/issues/3485>`_)
+- Fixed "assertion failure in non-release Nim when compiling NimYAML"
+  (`#4869 <https://github.com/nim-lang/Nim/issues/4869>`_)
+- Fixed "A closure causes nimscript to fail with unhandled exception"
+  (`#4906 <https://github.com/nim-lang/Nim/issues/4906>`_)
+- Fixed "startProcess changes working directory"
+  (`#4867 <https://github.com/nim-lang/Nim/issues/4867>`_)
+- Fixed "bindsym to void template produces ICE"
+  (`#4808 <https://github.com/nim-lang/Nim/issues/4808>`_)
+- Fixed "readline(TFile, var string) segfaults if second argument is nil"
+  (`#564 <https://github.com/nim-lang/Nim/issues/564>`_)
+- Fixed "times.parse gives the wrong day of the week for the first hour of the day."
+  (`#4922 <https://github.com/nim-lang/Nim/issues/4922>`_)
+- Fixed "Internal error when passing parameter proc inside .gcsafe closure"
+  (`#4927 <https://github.com/nim-lang/Nim/issues/4927>`_)
+- Fixed "Upcoming asyncdispatch doesn't compile with C++ backend on OS X"
+  (`#4928 <https://github.com/nim-lang/Nim/issues/4928>`_)
diff --git a/web/news/e029_version_0_16_0.rst b/web/news/e029_version_0_16_0.rst
new file mode 100644
index 000000000..a6c8aa20f
--- /dev/null
+++ b/web/news/e029_version_0_16_0.rst
@@ -0,0 +1,60 @@
+Version 0.16.0 released
+=======================
+
+.. container:: metadata
+
+  Posted by xyz on dd/mm/yyyy
+
+We're happy to announce that the latest release of Nim, version 0.16.0, is now
+available!
+
+As always, you can grab the latest version from the
+`downloads page <http://nim-lang.org/download.html>`_.
+
+This release includes almost xyz bug fixes and improvements. To see a full list
+of changes, take a look at the detailed changelog
+`below <#changelog>`_.
+
+Some of the most significant changes in this release include: xyz
+
+
+Changelog
+~~~~~~~~~
+
+Changes affecting backwards compatibility
+-----------------------------------------
+
+- ``staticExec`` now uses the directory of the nim file that contains the
+  ``staticExec`` call as the current working directory.
+- ``TimeInfo.tzname`` has been removed from ``times`` module because it was
+  broken. Because of this, the option ``"ZZZ"`` will no longer work in format
+  strings for formatting and parsing.
+
+Library Additions
+-----------------
+
+- Added new parameter to ``error`` proc of ``macro`` module to provide better
+  error message
+- Added new ``deques`` module intended to replace ``queues``.
+  ``deques`` provides a superset of ``queues`` API with clear naming.
+  ``queues`` module is now deprecated and will be removed in the future.
+
+Tool Additions
+--------------
+
+
+Compiler Additions
+------------------
+
+
+Language Additions
+------------------
+
+
+Bugfixes
+--------
+
+The list below has been generated based on the commits in Nim's git
+repository. As such it lists only the issues which have been closed
+via a commit, for a full list see
+`this link on Github <https://github.com/nim-lang/Nim/issues?utf8=%E2%9C%93&q=is%3Aissue+closed%3A%222016-06-22+..+2016-09-30%22+>`_.
diff --git a/web/news/e030_nim_in_action_in_production.rst b/web/news/e030_nim_in_action_in_production.rst
new file mode 100644
index 000000000..b68b82801
--- /dev/null
+++ b/web/news/e030_nim_in_action_in_production.rst
@@ -0,0 +1,53 @@
+Nim in Action is going into production!
+=======================================
+
+.. container:: metadata
+
+  Posted by Dominik Picheta on 20/11/2016
+
+.. raw::html
+
+  <a href="https://manning.com/books/nim-in-action?a_aid=niminaction&a_bid=78a27e81">
+    <img src="../assets/niminaction/banner2.png" alt="A printed copy of Nim in Action should be available in March 2017!" width="682"/>
+  </a>
+
+I am very happy to say that just last week I have put the finishing touches
+on Nim in Action. The final manuscript has been submitted to Manning (the book's
+publisher), and the printed version is expected to start shipping in March
+2017 (give or take 1 month).
+
+The eBook is still available and now contains all of the book's chapters,
+including new ones dealing with the foreign function interface and
+metaprogramming.
+That said, it may still take some time before the eBook is updated with the
+latest corrections.
+
+I am incredibly thankful to everyone that purchased the book already. Many of
+you have also given me a lot of `brilliant <http://forum.nim-lang.org/t/1978>`_
+`feedback <https://forums.manning.com/forums/nim-in-action>`_,
+thank you very much for
+taking the time to do so. I have done my best to act on this
+feedback and I hope you will agree that the book has risen in quality as a
+result.
+
+Writing this book has been both exhausting and incredible at the same time.
+I look forward
+to having a physical copy of it in my hands, and I'm sure many of you do as
+well. I can safely say that without your support this book would not have
+happened, even if you did not purchase a copy your interest in Nim has made it
+possible and I thank you for that.
+
+As always, you can make a purchase on
+`Manning's website <https://manning.com/books/nim-in-action?a_aid=niminaction&a_bid=78a27e81>`_.
+Both eBook's and printed books are available, and purchasing a printed book will
+get you an eBook for free.
+You can now also pre-order Nim in Action on
+`Amazon <https://www.amazon.co.uk/Nim-Action-Dominik-Picheta/dp/1617293431/ref=sr_1_1?ie=UTF8&qid=1479663850&sr=8-1&keywords=nim+in+action>`_!
+
+If you would like updates about the book then please feel free to
+follow either `myself <https://twitter.com/d0m96>`_ or
+`@nim_lang <https://twitter.com/nim_lang>`_ on Twitter. Finally, if you have any
+questions, do get in touch via `Twitter, NimForum,
+IRC or Gitter <http://nim-lang.org/community.html>`_.
+
+Thanks for reading!
diff --git a/web/support.rst b/web/support.rst
index 9a526605e..72e6dad71 100644
--- a/web/support.rst
+++ b/web/support.rst
@@ -32,9 +32,9 @@ Commercial support includes:
 All interested parties should email ``support@nim-lang.org``.
 The bid for contracting work is a commercial offer provided by:
 
-| **METATEXX GmbH**
-| Spicher Str. 30
-| 53859 Niederkassel
+
+| **Andreas Rumpf**
+| St.-Quentin-Ring 47
+| 67663 Kaiserslautern
 | GERMANY
-| EU VAT-IN: DE287088604
-| http://metatexx.de/index.php?index=12
+| EU VAT-IN: DE297783450
diff --git a/web/ticker.html b/web/ticker.html
index a05ea8476..86dc97c14 100644
--- a/web/ticker.html
+++ b/web/ticker.html
@@ -1,26 +1,26 @@
-<a class="news" href="$1news/2016_09_30_version_0_15_0_released.html">
+<a class="news" href="$1news/e030_nim_in_action_in_production.html">
+  <h4>November 20, 2016</h4>
+  <p>Nim in Action is going into production!</p>
+</a>
+
+<a class="news" href="$1news/e028_version_0_15_2.html">
+  <h4>October 23, 2016</h4>
+  <p>Nim version 0.15.2 has been released!</p>
+</a>
+
+<a class="news" href="$1news/e027_version_0_15_0.html">
   <h4>September 30, 2016</h4>
   <p>Nim version 0.15.0 has been released!</p>
 </a>
 
-<a class="news" href="$1news/2016_09_03_nim_community_survey_results.html">
+<a class="news" href="$1news/e026_survey_results.html">
   <h4>September 3, 2016</h4>
   <p>Nim Community Survey results</p>
 </a>
 
-<a class="news" href="$1news/2016_08_06_bountysource_update_the_road_to_v10.html">
+<a class="news" href="$1news/e025_bountysource_update.html">
   <h4>August 6, 2016</h4>
   <p>BountySource Update: The Road to v1.0</p>
 </a>
 
-<a class="news" href="$1news/2016_06_23_launching_the_2016_nim_community_survey.html">
-  <h4>June 23, 2016</h4>
-  <p>Launching the 2016 Nim community survey!</p>
-</a>
-
-<a class="news" href="$1news/2016_06_11_version_0_14_2_released.html">
-  <h4>June 11, 2016</h4>
-  <p>Nim version 0.14.2 has been released!</p>
-</a>
-
 <a href="$1news.html" class="blue">See All News...</a>
diff --git a/web/website.ini b/web/website.ini
index 860ab9338..3b8203cc0 100644
--- a/web/website.ini
+++ b/web/website.ini
@@ -31,7 +31,7 @@ file: ticker.html
 [Documentation]
 doc: "endb;intern;apis;lib;manual.rst;tut1.rst;tut2.rst;nimc;overview;filters"
 doc: "tools;niminst;nimgrep;gc;estp;idetools;docgen;koch;backends.rst"
-doc: "nimfix.rst;nimsuggest.rst;nep1.rst;nims.rst"
+doc: "nimfix.rst;nimsuggest.rst;nep1.rst;nims.rst;contributing.rst"
 pdf: "manual.rst;lib.rst;tut1.rst;tut2.rst;nimc.rst;niminst.rst;gc.rst"
 srcdoc2: "system.nim;system/nimscript;pure/ospaths"
 srcdoc2: "core/macros;pure/marshal;core/typeinfo"
@@ -45,13 +45,13 @@ srcdoc2: "pure/parseopt;pure/parseopt2;pure/hashes;pure/strtabs;pure/lexbase"
 srcdoc2: "pure/parsecfg;pure/parsexml;pure/parsecsv;pure/parsesql"
 srcdoc2: "pure/streams;pure/terminal;pure/cgi;pure/unicode;pure/strmisc"
 srcdoc2: "pure/htmlgen;pure/parseutils;pure/browsers"
-srcdoc2: "impure/db_postgres;impure/db_mysql;impure/db_sqlite"
+srcdoc2: "impure/db_postgres;impure/db_mysql;impure/db_sqlite;pure/db_common"
 srcdoc2: "pure/httpserver;pure/httpclient;pure/smtp;impure/ssl;pure/fsmonitor"
 srcdoc2: "pure/ropes;pure/unidecode/unidecode;pure/xmldom;pure/xmldomparser"
 srcdoc2: "pure/xmlparser;pure/htmlparser;pure/xmltree;pure/colors;pure/mimetypes"
 srcdoc2: "pure/json;pure/base64;pure/scgi"
 srcdoc2: "pure/collections/tables;pure/collections/sets;pure/collections/lists"
-srcdoc2: "pure/collections/intsets;pure/collections/queues;pure/encodings"
+srcdoc2: "pure/collections/intsets;pure/collections/queues;pure/collections/deques;pure/encodings"
 srcdoc2: "pure/events;pure/collections/sequtils;pure/cookies"
 srcdoc2: "pure/memfiles;pure/subexes;pure/collections/critbits"
 srcdoc2: "deprecated/pure/asyncio;deprecated/pure/actors;core/locks;core/rlocks;pure/oids;pure/endians;pure/uri"
@@ -63,7 +63,7 @@ srcdoc2: "deprecated/pure/ftpclient"
 srcdoc2: "pure/asyncfile;pure/asyncftpclient"
 srcdoc2: "pure/md5;pure/rationals"
 srcdoc2: "posix/posix"
-srcdoc2: "pure/fenv;pure/securehash"
+srcdoc2: "pure/fenv;pure/securehash;impure/rdstdin"
 srcdoc2: "pure/basic2d;pure/basic3d;pure/mersenne;pure/coro;pure/httpcore"
 
 ; Note: everything under 'webdoc' doesn't get listed in the index, so wrappers