summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--bootstrap.sh20
-rw-r--r--compiler/ccgexprs.nim4
-rw-r--r--compiler/cgen.nim6
-rw-r--r--compiler/depends.nim4
-rw-r--r--compiler/docgen2.nim4
-rw-r--r--compiler/importer.nim2
-rw-r--r--compiler/jsgen.nim6
-rw-r--r--compiler/main.nim193
-rw-r--r--compiler/modulegraphs.nim108
-rw-r--r--compiler/modules.nim312
-rw-r--r--compiler/nim.nim8
-rw-r--r--compiler/nimsuggest/nimsuggest.nim12
-rw-r--r--compiler/passaux.nim4
-rw-r--r--compiler/passes.nim35
-rw-r--r--compiler/rodwrite.nim4
-rw-r--r--compiler/scriptconfig.nim14
-rw-r--r--compiler/sem.nim10
-rw-r--r--compiler/semdata.nim9
-rw-r--r--compiler/semstmts.nim4
-rw-r--r--compiler/suggest.nim2
-rw-r--r--compiler/vm.nim4
-rw-r--r--doc/basicopt.txt2
-rw-r--r--doc/intern.txt2
-rw-r--r--lib/pure/htmlparser.nim11
-rw-r--r--lib/pure/os.nim1
-rw-r--r--lib/pure/osproc.nim6
-rw-r--r--lib/pure/parsexml.nim4
-rw-r--r--lib/pure/strutils.nim10
-rw-r--r--lib/pure/times.nim239
-rw-r--r--tests/stdlib/ttime.nim128
-rw-r--r--tools/finish.nim6
-rw-r--r--tools/nimsuggest/nimsuggest.nim78
-rw-r--r--tools/nimsuggest/tester.nim123
-rw-r--r--tools/nimsuggest/tests/dep_v1.nim8
-rw-r--r--tools/nimsuggest/tests/dep_v2.nim9
-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.nim4
-rw-r--r--web/news/e029_version_0_16_0.rst4
41 files changed, 797 insertions, 667 deletions
diff --git a/.gitignore b/.gitignore
index 57b8a68d4..f5b8e4826 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ tags
 install.sh
 deinstall.sh
 
+doc/html/
 doc/*.html
 doc/*.pdf
 doc/*.idx
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/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index be49ddc87..00a60fe1b 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -592,9 +592,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
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index 86b0d60d0..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:
@@ -1174,7 +1176,7 @@ proc newModule(module: PSym): BModule =
     if (sfDeadCodeElim in module.flags):
       internalError("added pending module twice: " & module.filename)
 
-proc myOpen(module: PSym; cache: IdentCache): 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
@@ -1209,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/depends.nim b/compiler/depends.nim
index f8b8a3d71..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; cache: IdentCache): PPassContext =
+proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
   var g: PGen
   new(g)
   g.module = module
diff --git a/compiler/docgen2.nim b/compiler/docgen2.nim
index 17b6caaba..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; cache: IdentCache): PPassContext =
+proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
   var g: PGen
   new(g)
   g.module = module
diff --git a/compiler/importer.nim b/compiler/importer.nim
index 771ab35d0..ce365c4dc 100644
--- a/compiler/importer.nim
+++ b/compiler/importer.nim
@@ -162,7 +162,7 @@ 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, c.cache))
+    result = importModuleAs(n, gImportModule(c.graph, c.module, f, c.cache))
     # we cannot perform this check reliably because of
     # test: modules/import_in_config)
     if result.info.fileIndex == c.module.info.fileIndex and
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index eaa492a27..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
@@ -2272,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; cache: IdentCache): 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/main.nim b/compiler/main.nim
index 103eef877..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(cache: IdentCache) =
+proc commandGenDepend(graph: ModuleGraph; cache: IdentCache) =
   semanticPasses()
   registerPass(gendependPass)
   registerPass(cleanupPass)
-  compileProject(cache)
+  compileProject(graph, cache)
   generateDot(gProjectFull)
   execExternalProgram("dot -Tpng -o" & changeFileExt(gProjectFull, "png") &
       ' ' & changeFileExt(gProjectFull, "dot"))
 
-proc commandCheck(cache: IdentCache) =
+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(cache)
+  compileProject(graph, cache)
 
-proc commandDoc2(cache: IdentCache; 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(cache)
+  compileProject(graph, cache)
   finishDoc2Pass(gProjectName)
 
-proc commandCompileToC(cache: IdentCache) =
+proc commandCompileToC(graph: ModuleGraph; cache: IdentCache) =
   extccomp.initVars()
   semanticPasses()
   registerPass(cgenPass)
   rodPass()
   #registerPass(cleanupPass())
 
-  compileProject(cache)
+  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(cache: IdentCache) =
+proc commandCompileToJS(graph: ModuleGraph; cache: IdentCache) =
   #incl(gGlobalOptions, optSafeCode)
   setTarget(osJS, cpuJS)
   #initDefines()
@@ -113,9 +78,9 @@ proc commandCompileToJS(cache: IdentCache) =
   if gCmd == cmdCompileToPHP: defineSymbol("nimphp")
   semanticPasses()
   registerPass(JSgenPass)
-  compileProject(cache)
+  compileProject(graph, cache)
 
-proc interactivePasses(cache: IdentCache) =
+proc interactivePasses(graph: ModuleGraph; cache: IdentCache) =
   #incl(gGlobalOptions, optSafeCode)
   #setTarget(osNimrodVM, cpuNimrodVM)
   initDefines()
@@ -125,28 +90,28 @@ proc interactivePasses(cache: IdentCache) =
   registerPass(semPass)
   registerPass(evalPass)
 
-proc commandInteractive(cache: IdentCache) =
+proc commandInteractive(graph: ModuleGraph; cache: IdentCache) =
   msgs.gErrorMax = high(int)  # do not stop after first error
-  interactivePasses(cache)
-  compileSystemModule(cache)
+  interactivePasses(graph, cache)
+  compileSystemModule(graph, cache)
   if commandArgs.len > 0:
-    discard compileModule(fileInfoIdx(gProjectFull), cache, {})
+    discard graph.compileModule(fileInfoIdx(gProjectFull), cache, {})
   else:
-    var m = makeStdinModule()
+    var m = graph.makeStdinModule()
     incl(m.flags, sfMainModule)
-    processModule(m, llStreamOpenStdIn(), nil, cache)
+    processModule(graph, m, llStreamOpenStdIn(), nil, cache)
 
 const evalPasses = [verbosePass, semPass, evalPass]
 
-proc evalNim(nodes: PNode, module: PSym; cache: IdentCache) =
-  carryPasses(nodes, module, cache, evalPasses)
+proc evalNim(graph: ModuleGraph; nodes: PNode, module: PSym; cache: IdentCache) =
+  carryPasses(graph, nodes, module, cache, evalPasses)
 
-proc commandEval(cache: IdentCache; exp: string) =
+proc commandEval(graph: ModuleGraph; cache: IdentCache; exp: string) =
   if systemModule == nil:
-    interactivePasses(cache)
-    compileSystemModule(cache)
+    interactivePasses(graph, cache)
+    compileSystemModule(graph, cache)
   let echoExp = "echo \"eval\\t\", " & "repr(" & exp & ")"
-  evalNim(echoExp.parseString(cache), makeStdinModule(), cache)
+  evalNim(graph, echoExp.parseString(cache), makeStdinModule(graph), cache)
 
 proc commandScan(cache: IdentCache) =
   var f = addFileExt(mainCommandArg(), NimExt)
@@ -165,75 +130,11 @@ proc commandScan(cache: IdentCache) =
   else:
     rawMessage(errCannotOpenFile, f)
 
-proc commandSuggest(cache: IdentCache) =
-  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(cache)
-    let gDirtyBufferIdx = gTrackPos.fileIndex
-    discard compileModule(gDirtyBufferIdx, cache, {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(cache) #(projFile)
-
-proc resetMemory =
-  resetCompilationLists()
-  ccgutils.resetCaches()
-  resetAllModules()
-  resetRopeCache()
-  resetSysTypes()
-  gOwners = @[]
-  resetIdentCache()
-
-  # 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*(cache: IdentCache) =
+proc mainCommand*(graph: ModuleGraph; cache: IdentCache) =
   when SimulateCaasMemReset:
     gGlobalOptions.incl(optCaasEnabled)
 
@@ -249,28 +150,28 @@ proc mainCommand*(cache: IdentCache) =
   of "c", "cc", "compile", "compiletoc":
     # compile means compileToC currently
     gCmd = cmdCompileToC
-    commandCompileToC(cache)
+    commandCompileToC(graph, cache)
   of "cpp", "compiletocpp":
     gCmd = cmdCompileToCpp
     defineSymbol("cpp")
-    commandCompileToC(cache)
+    commandCompileToC(graph, cache)
   of "objc", "compiletooc":
     gCmd = cmdCompileToOC
     defineSymbol("objc")
-    commandCompileToC(cache)
+    commandCompileToC(graph, cache)
   of "run":
     gCmd = cmdRun
     when hasTinyCBackend:
       extccomp.setCC("tcc")
-      commandCompileToC(cache)
+      commandCompileToC(graph, cache)
     else:
       rawMessage(errInvalidCommandX, command)
   of "js", "compiletojs":
     gCmd = cmdCompileToJS
-    commandCompileToJS(cache)
+    commandCompileToJS(graph, cache)
   of "php":
     gCmd = cmdCompileToPHP
-    commandCompileToJS(cache)
+    commandCompileToJS(graph, cache)
   of "doc":
     wantMainModule()
     gCmd = cmdDoc
@@ -280,7 +181,7 @@ proc mainCommand*(cache: IdentCache) =
     gCmd = cmdDoc
     loadConfigs(DocConfig, cache)
     defineSymbol("nimdoc")
-    commandDoc2(cache, false)
+    commandDoc2(graph, cache, false)
   of "rst2html":
     gCmd = cmdRst2html
     loadConfigs(DocConfig, cache)
@@ -301,14 +202,14 @@ proc mainCommand*(cache: IdentCache) =
     loadConfigs(DocConfig, cache)
     wantMainModule()
     defineSymbol("nimdoc")
-    commandDoc2(cache, true)
+    commandDoc2(graph, cache, true)
   of "buildindex":
     gCmd = cmdDoc
     loadConfigs(DocConfig, cache)
     commandBuildIndex()
   of "gendepend":
     gCmd = cmdGenDepend
-    commandGenDepend(cache)
+    commandGenDepend(graph, cache)
   of "dump":
     gCmd = cmdDump
     if getConfigVar("dump.format") == "json":
@@ -337,7 +238,7 @@ proc mainCommand*(cache: IdentCache) =
       for it in iterSearchPath(searchPaths): msgWriteln(it)
   of "check":
     gCmd = cmdCheck
-    commandCheck(cache)
+    commandCheck(graph, cache)
   of "parse":
     gCmd = cmdParse
     wantMainModule()
@@ -346,26 +247,12 @@ proc mainCommand*(cache: IdentCache) =
     gCmd = cmdScan
     wantMainModule()
     commandScan(cache)
-    msgWriteln("Beware: Indentation tokens depend on the parser\'s state!")
+    msgWriteln("Beware: Indentation tokens depend on the parser's state!")
   of "secret":
     gCmd = cmdInteractive
-    commandInteractive(cache)
+    commandInteractive(graph, cache)
   of "e":
-    # XXX: temporary command for easier testing
-    commandEval(cache, mainCommandArg())
-  of "reset":
-    resetMemory()
-  of "idetools":
-    gCmd = cmdIdeTools
-    if gEvalExpr != "":
-      commandEval(cache, gEvalExpr)
-    else:
-      commandSuggest(cache)
-  of "serve":
-    isServing = true
-    gGlobalOptions.incl(optCaasEnabled)
-    msgs.gErrorMax = high(int)  # do not stop after first error
-    serve(cache, mainCommand)
+    commandEval(graph, cache, mainCommandArg())
   of "nop", "help":
     # prevent the "success" message:
     gCmd = cmdDump
@@ -393,4 +280,4 @@ proc mainCommand*(cache: IdentCache) =
 
   resetAttributes()
 
-proc mainCommand*() = mainCommand(newIdentCache())
+proc mainCommand*() = mainCommand(newModuleGraph(), newIdentCache())
diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim
new file mode 100644
index 000000000..9a3caa663
--- /dev/null
+++ b/compiler/modulegraphs.nim
@@ -0,0 +1,108 @@
+#
+#
+#           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
+
+{.this: g.}
+
+proc newModuleGraph*(): ModuleGraph =
+  result = ModuleGraph()
+  initStrTable(result.packageSyms)
+  result.deps = initIntSet()
+  result.modules = @[]
+  result.inclToMod = initTable[int32, int32]()
+
+proc resetAllModules*(g: ModuleGraph) =
+  initStrTable(packageSyms)
+  deps = initIntSet()
+  modules = @[]
+  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 be1de16d9..26ca2177b 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,13 +157,12 @@ proc newModule(fileIdx: int32): PSym =
   # strTableIncl() for error corrections:
   discard strTableIncl(packSym.tab, result)
 
-proc compileModule*(fileIdx: int32; cache: IdentCache, 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:
@@ -182,44 +171,52 @@ proc compileModule*(fileIdx: int32; cache: IdentCache, flags: TSymFlags): PSym =
     if gCmd in {cmdCompileToC, cmdCompileToCpp, cmdCheck, cmdIdeTools}:
       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,
+    discard processModule(graph, result,
       if sfMainModule in flags and gProjectIsStdin: stdin.llStreamOpen else: nil,
       rd, cache)
-    if optCaasEnabled in gGlobalOptions:
-      gMemCacheData[fileIdx].compiledAt = gLastCmdTime
-      gMemCacheData[fileIdx].needsRecompile = Recompiled
-      if validFile: doHash fileIdx
-  else:
-    if checkDepMem(fileIdx) == Yes:
-      result = compileModule(fileIdx, cache, flags)
-    else:
-      result = gCompiledModules[fileIdx]
-
-proc importModule*(s: PSym, fileIdx: int32; cache: IdentCache): PSym {.procvar.} =
+    #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, cache, {})
-  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; cache: IdentCache): PNode {.procvar.} =
+proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: int32;
+                    cache: IdentCache): PNode {.procvar.} =
   result = syntaxes.parseFile(fileIdx, cache)
-  if optCaasEnabled in gGlobalOptions:
-    growCache gMemCacheData, fileIdx
-    addDep(s, fileIdx)
-    doHash(fileIdx)
+  graph.addDep(s, fileIdx)
+  graph.addIncludeDep(s.position.int32, fileIdx)
 
-proc compileSystemModule*(cache: IdentCache) =
+proc compileSystemModule*(graph: ModuleGraph; cache: IdentCache) =
   if magicsys.systemModule == nil:
     systemFileIdx = fileInfoIdx(options.libpath/"system.nim")
-    discard compileModule(systemFileIdx, cache, {sfSystemModule})
+    discard graph.compileModule(systemFileIdx, cache, {sfSystemModule})
 
 proc wantMainModule* =
   if gProjectFull.len == 0:
@@ -229,18 +226,19 @@ proc wantMainModule* =
 passes.gIncludeFile = includeModule
 passes.gImportModule = importModule
 
-proc compileProject*(cache: IdentCache; 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
   if projectFile == systemFileIdx:
-    discard compileModule(projectFile, cache, {sfMainModule, sfSystemModule})
+    discard graph.compileModule(projectFile, cache, {sfMainModule, sfSystemModule})
   else:
-    compileSystemModule(cache)
-    discard compileModule(projectFile, cache, {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/nim.nim b/compiler/nim.nim
index aeab9421e..f8d6b607a 100644
--- a/compiler/nim.nim
+++ b/compiler/nim.nim
@@ -14,14 +14,14 @@ when defined(gcc) and defined(windows):
     {.link: "icons/nim_icon.o".}
 
 when defined(amd64) and defined(windows) and defined(vcc):
-  {.link: "icons/nim-amd64-windows-vcc.res" .}
+  {.link: "icons/nim-amd64-windows-vcc.res".}
 when defined(i386) and defined(windows) and defined(vcc):
-  {.link: "icons/nim-i386-windows-vcc.res" .}
+  {.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, idents
+  nodejs, scriptconfig, idents, modulegraphs
 
 when hasTinyCBackend:
   import tccgen
@@ -73,7 +73,7 @@ proc handleCmdLine(cache: IdentCache) =
     processCmdLine(passCmd2, "")
     if options.command == "":
       rawMessage(errNoCommand, command)
-    mainCommand(cache)
+    mainCommand(newModuleGraph(), cache)
     if optHints in gOptions and hintGCStats in gNotes: echo(GC_getStatistics())
     #echo(GC_getStatistics())
     if msgs.gErrorCounter == 0:
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/passaux.nim b/compiler/passaux.nim
index dc99ffd0c..eeaf12953 100644
--- a/compiler/passaux.nim
+++ b/compiler/passaux.nim
@@ -12,7 +12,9 @@
 import
   strutils, ast, astalgo, passes, idents, msgs, options, idgen
 
-proc verboseOpen(s: PSym; cache: IdentCache): 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 9e383849a..4f1d4e3aa 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; cache: IdentCache): 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; cache: IdentCache): PSym {.nimcall.}
-  gIncludeFile*: proc (m: PSym, fileIdx: int32; cache: IdentCache): 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,29 +90,32 @@ proc registerPass*(p: TPass) =
   gPasses[gPassesLen] = p
   inc(gPassesLen)
 
-proc carryPass*(p: TPass, module: PSym; cache: IdentCache;
+proc carryPass*(g: ModuleGraph; p: TPass, module: PSym; cache: IdentCache;
                 m: TPassData): TPassData =
-  var c = p.open(module, cache)
+  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; cache: IdentCache; 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, cache, passdata)
+    passdata = carryPass(g, pass, module, cache, passdata)
 
-proc openPasses(a: var TPassContextArray, module: PSym; cache: IdentCache) =
+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, cache)
+      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:
@@ -155,7 +158,7 @@ proc processImplicits(implicits: seq[string], nodeKind: TNodeKind,
     importStmt.addSon str
     if not processTopLevelStmt(importStmt, a): break
 
-proc processModule*(module: PSym, stream: PLLStream,
+proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream,
                     rd: PRodReader; cache: IdentCache): bool {.discardable.} =
   var
     p: TParsers
@@ -163,7 +166,7 @@ proc processModule*(module: PSym, stream: PLLStream,
     s: PLLStream
     fileIdx = module.fileIdx
   if rd == nil:
-    openPasses(a, module, cache)
+    openPasses(graph, a, module, cache)
     if stream == nil:
       let filename = fileIdx.toFullPathConsiderDirty
       s = llStreamOpen(filename, fmRead)
@@ -203,7 +206,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/rodwrite.nim b/compiler/rodwrite.nim
index 4e8b27733..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
@@ -633,7 +635,7 @@ proc process(c: PPassContext, n: PNode): PNode =
   else:
     discard
 
-proc myOpen(module: PSym; cache: IdentCache): 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, cache)
   rawAddInterfaceSym(w, module)
diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim
index 891ee80ad..1105d3b67 100644
--- a/compiler/scriptconfig.nim
+++ b/compiler/scriptconfig.nim
@@ -13,7 +13,7 @@
 import
   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
@@ -134,9 +134,11 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string): PEvalContext
   cbconf selfExe:
     setResult(a, os.getAppFilename())
 
-proc runNimScript*(cache: IdentCache; 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*(cache: IdentCache; 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, cache, scriptName)
 
-  compileSystemModule(cache)
-  discard processModule(m, llStreamOpen(scriptName, fmRead), nil, cache)
+  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 e5ce7c6ce..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
 
@@ -398,8 +400,8 @@ proc addCodeForGenerics(c: PContext, n: PNode) =
         addSon(n, prc.ast)
   c.lastGenericIdx = c.generics.len
 
-proc myOpen(module: PSym; cache: IdentCache): PPassContext =
-  var c = newContext(module, cache)
+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; cache: IdentCache): PPassContext =
     gNotes = ForeignPackageNotes
   result = c
 
-proc myOpenCached(module: PSym; rd: PRodReader): PPassContext =
-  result = myOpen(module, rd.cache)
+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/semdata.nim b/compiler/semdata.nim
index 9a6011834..5b84b7cdf 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
@@ -107,6 +108,7 @@ type
                             op: TTypeAttachedOp; col: int): PSym {.nimcall.}
     selfName*: PIdent
     cache*: IdentCache
+    graph*: ModuleGraph
     signatures*: TStrTable
 
 proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair =
@@ -151,7 +153,7 @@ proc newOptionEntry*(): POptionEntry =
   result.dynlib = nil
   result.notes = gNotes
 
-proc newContext*(module: PSym; cache: IdentCache): PContext =
+proc newContext*(graph: ModuleGraph; module: PSym; cache: IdentCache): PContext =
   new(result)
   result.ambiguousSymbols = initIntSet()
   initLinkedList(result.optionStack)
@@ -166,6 +168,7 @@ proc newContext*(module: PSym; cache: IdentCache): PContext =
   result.generics = @[]
   result.unknownIdents = initIntSet()
   result.cache = cache
+  result.graph = graph
   initStrTable(result.signatures)
 
 
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index f8ec00b23..0c6f6848e 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -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, c.cache)
+            let code = gIncludeFile(c.graph, c.module, f, c.cache)
             gatherStmts c, code, result
             excl(c.includedFiles, f)
     of nkStmtList:
@@ -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, c.cache)))
+        addSon(result, semStmt(c, gIncludeFile(c.graph, c.module, f, c.cache)))
         excl(c.includedFiles, f)
 
 proc setLine(n: PNode, info: TLineInfo) =
diff --git a/compiler/suggest.nim b/compiler/suggest.nim
index 558581e04..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, c.cache)
+        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/vm.nim b/compiler/vm.nim
index 8e1c9f661..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
 
@@ -1516,7 +1518,7 @@ proc setupGlobalCtx(module: PSym; cache: IdentCache) =
   else:
     refresh(globalCtx, module)
 
-proc myOpen(module: PSym; cache: IdentCache): PPassContext =
+proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
   #var c = newEvalContext(module, emRepl)
   #c.features = {allowCast, allowFFI, allowInfiniteLoops}
   #pushStackFrame(c, newStackFrame())
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/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/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/os.nim b/lib/pure/os.nim
index 8f8f36b80..f077e798a 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -613,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",
diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim
index 44ec5b548..76bd2dfe1 100644
--- a/lib/pure/osproc.nim
+++ b/lib/pure/osproc.nim
@@ -841,15 +841,19 @@ elif not defined(useNimRtl):
       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/parsexml.nim b/lib/pure/parsexml.nim
index aa4a13ecf..978c9c516 100644
--- a/lib/pure/parsexml.nim
+++ b/lib/pure/parsexml.nim
@@ -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)
 
diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim
index bfc32bc71..129869373 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".} =
diff --git a/lib/pure/times.nim b/lib/pure/times.nim
index db09f94c1..1e869d301 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>".} = 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.}
@@ -154,9 +146,11 @@ type
                               ## 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
     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 +178,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 +188,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 +206,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.
 
@@ -369,7 +353,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))
@@ -389,7 +373,7 @@ proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
   intval.months = - interval.months
   intval.years = - interval.years
   let secs = toSeconds(a, intval)
-  if a.tzname == "UTC":
+  if a.timezone == 0:
     result = getGMTime(fromSeconds(t + secs))
   else:
     result = getLocalTime(fromSeconds(t + secs))
@@ -424,7 +408,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 +446,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 +458,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 +519,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 +544,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 +611,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 +728,12 @@ 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
+  epochStartYear = 1970
+
 proc formatToken(info: TimeInfo, token: string, buf: var string) =
   ## Helper of the format proc to parse individual tokens.
   ##
@@ -891,24 +823,28 @@ 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 hours = abs(info.timezone) div secondsInHour
+    if info.timezone < 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 hours = abs(info.timezone) div secondsInHour
+    if info.timezone < 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
+      hours = abs(info.timezone) div secondsInHour
+      minutes = abs(info.timezone) mod 60
+    if info.timezone < 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 +881,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 +919,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) =
@@ -1142,34 +1089,33 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
     j += 4
   of "z":
     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
     else:
       raise newException(ValueError,
         "Couldn't parse timezone offset (z), got: " & value[j])
     j += 2
   of "zz":
     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
     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()
+    var factor = 0
+    if value[j] == '+': factor = -1
+    elif value[j] == '-': factor = 1
     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
@@ -1203,8 +1149,7 @@ proc parse*(value, layout: string): TimeInfo =
   ##    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
@@ -1257,7 +1202,7 @@ proc parse*(value, layout: string): TimeInfo =
   let correctDST = getLocalTime(toTime(info))
   info.isDST = correctDST.isDST
 
-  # Now we preocess it again with the correct isDST to correct things like
+  # Now we process it again with the correct isDST to correct things like
   # weekday and yearday.
   return getLocalTime(toTime(info))
 
@@ -1290,12 +1235,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/tests/stdlib/ttime.nim b/tests/stdlib/ttime.nim
index 065009535..5d3c8325e 100644
--- a/tests/stdlib/ttime.nim
+++ b/tests/stdlib/ttime.nim
@@ -9,77 +9,93 @@ 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)
 # Kitchen     = "3:04PM"
 parseTestTimeOnly("3:04PM", "h:mmtt", "15:04:00")
 #when not defined(testing):
@@ -101,21 +117,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
@@ -164,3 +179,14 @@ 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
diff --git a/tools/finish.nim b/tools/finish.nim
index 8b9acfc56..cac001d79 100644
--- a/tools/finish.nim
+++ b/tools/finish.nim
@@ -114,8 +114,10 @@ proc main() =
     var mingWchoices: seq[string] = @[]
     var incompat: 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 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):
diff --git a/tools/nimsuggest/nimsuggest.nim b/tools/nimsuggest/nimsuggest.nim
index c6a6bce05..822ef7224 100644
--- a/tools/nimsuggest/nimsuggest.nim
+++ b/tools/nimsuggest/nimsuggest.nim
@@ -17,7 +17,7 @@ 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/idents, compiler/modulegraphs
 
 when defined(windows):
   import winlean
@@ -60,7 +60,7 @@ var
 
 const
   seps = {':', ';', ' ', '\t'}
-  Help = "usage: sug|con|def|use|dus|chk|highlight|outline file.nim[;dirtyfile.nim]:line:col\n" &
+  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"
@@ -104,13 +104,13 @@ proc sexp(s: seq[Suggest]): SexpNode =
   for sug in s:
     result.add(sexp(sug))
 
-proc listEPC(): SexpNode =
+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"]:
+  for command in ["sug", "con", "def", "use", "dus", "chk", "mod"]:
     let
       cmd = sexp(command)
       methodDesc = newSList()
@@ -128,19 +128,19 @@ proc findNode(n: PNode): PSym =
       let res = n.sons[i].findNode
       if res != nil: return res
 
-proc symFromInfo(gTrackPos: TLineInfo): PSym =
-  let m = getModule(gTrackPos.fileIndex)
+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;
-             cache: IdentCache) =
+             graph: ModuleGraph; cache: IdentCache) =
   if gLogging:
     logStr("cmd: " & $cmd & ", file: " & file & ", dirtyFile: " & dirtyfile & "[" & $line & ":" & $col & "]")
   gIdeCmd = cmd
   if cmd == ideUse and suggestVersion != 2:
-    modules.resetAllModules()
+    graph.resetAllModules()
   var isKnownFile = true
   let dirtyIdx = file.fileInfoIdx(isKnownFile)
 
@@ -152,23 +152,25 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int;
   if suggestVersion < 2:
     usageSym = nil
   if not isKnownFile:
-    compileProject(cache)
+    graph.compileProject(cache)
   if suggestVersion == 2 and gIdeCmd in {ideUse, ideDus} and
       dirtyfile.len == 0:
     discard "no need to recompile anything"
   else:
-    resetModule dirtyIdx
-    if dirtyIdx != gProjectMainIdx:
-      resetModule gProjectMainIdx
-    compileProject(cache, dirtyIdx)
+    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: symFromInfo(gTrackPos) else: usageSym
+    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; cache: IdentCache) =
+proc executeEpc(cmd: IdeCmd, args: SexpNode;
+                graph: ModuleGraph; cache: IdentCache) =
   let
     file = args[0].getStr
     line = args[1].getNum
@@ -176,7 +178,7 @@ proc executeEpc(cmd: IdeCmd, args: SexpNode; cache: IdentCache) =
   var dirtyfile = ""
   if len(args) > 3:
     dirtyfile = args[3].getStr(nil)
-  execute(cmd, file, dirtyfile, int(line), int(column), cache)
+  execute(cmd, file, dirtyfile, int(line), int(column), graph, cache)
 
 proc returnEpc(socket: var Socket, uid: BiggestInt, s: SexpNode|string,
                return_symbol = "return") =
@@ -192,7 +194,7 @@ template sendEpc(results: typed, tdef, hook: untyped) =
       else: s
     )
 
-  executeEpc(gIdeCmd, args, cache)
+  executeEpc(gIdeCmd, args, graph, cache)
   let res = sexp(results)
   if gLogging:
     logStr($res)
@@ -215,7 +217,7 @@ proc connectToNextFreePort(server: Socket, host: string): Port =
   let (_, port) = server.getLocalAddr
   result = port
 
-proc parseCmdLine(cmd: string; cache: IdentCache) =
+proc parseCmdLine(cmd: string; graph: ModuleGraph; cache: IdentCache) =
   template toggle(sw) =
     if sw in gGlobalOptions:
       excl(gGlobalOptions, sw)
@@ -235,6 +237,7 @@ proc parseCmdLine(cmd: string; cache: IdentCache) =
   of "def": gIdeCmd = ideDef
   of "use": gIdeCmd = ideUse
   of "dus": gIdeCmd = ideDus
+  of "mod": gIdeCmd = ideMod
   of "chk":
     gIdeCmd = ideChk
     incl(gGlobalOptions, optIdeDebug)
@@ -256,25 +259,25 @@ proc parseCmdLine(cmd: string; cache: IdentCache) =
   i += skipWhile(cmd, seps, i)
   i += parseInt(cmd, col, i)
 
-  execute(gIdeCmd, orig, dirtyfile, line, col-1, cache)
+  execute(gIdeCmd, orig, dirtyfile, line, col-1, graph, cache)
 
-proc serveStdin(cache: IdentCache) =
+proc serveStdin(graph: ModuleGraph; cache: IdentCache) =
   if gEmitEof:
     echo DummyEof
     while true:
       let line = readLine(stdin)
-      parseCmdLine line, cache
+      parseCmdLine line, graph, cache
       echo DummyEof
       flushFile(stdout)
   else:
     echo Help
     var line = ""
     while readLineFromStdin("> ", line):
-      parseCmdLine line, cache
+      parseCmdLine line, graph, cache
       echo ""
       flushFile(stdout)
 
-proc serveTcp(cache: IdentCache) =
+proc serveTcp(graph: ModuleGraph; cache: IdentCache) =
   var server = newSocket()
   server.bindAddr(gPort, gAddress)
   var inp = "".TaintedString
@@ -288,12 +291,12 @@ proc serveTcp(cache: IdentCache) =
     accept(server, stdoutSocket)
 
     stdoutSocket.readLine(inp)
-    parseCmdLine inp.string, cache
+    parseCmdLine inp.string, graph, cache
 
     stdoutSocket.send("\c\L")
     stdoutSocket.close()
 
-proc serveEpc(server: Socket; cache: IdentCache) =
+proc serveEpc(server: Socket; graph: ModuleGraph; cache: IdentCache) =
   var client = newSocket()
   # Wait for connection
   accept(server, client)
@@ -346,11 +349,7 @@ proc serveEpc(server: Socket; cache: IdentCache) =
                          "unexpected call: " & epcAPI
       raise newException(EUnexpectedCommand, errMessage)
 
-template beCompatible() =
-  when compiles(modules.gFuzzyGraphChecking):
-    modules.gFuzzyGraphChecking = true
-
-proc mainCommand(cache: IdentCache) =
+proc mainCommand(graph: ModuleGraph; cache: IdentCache) =
   clearPasses()
   registerPass verbosePass
   registerPass semPass
@@ -368,26 +367,23 @@ proc mainCommand(cache: IdentCache) =
 
   case gMode
   of mstdin:
-    beCompatible()
-    compileProject(cache)
+    compileProject(graph, cache)
     #modules.gFuzzyGraphChecking = false
-    serveStdin(cache)
+    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
-    beCompatible()
-    compileProject(cache)
+    compileProject(graph, cache)
     #modules.gFuzzyGraphChecking = false
-    serveTcp(cache)
+    serveTcp(graph, cache)
   of mepc:
-    beCompatible()
     var server = newSocket()
     let port = connectToNextFreePort(server, "localhost")
     server.listen()
     echo port
-    compileProject(cache)
-    serveEpc(server, cache)
+    compileProject(graph, cache)
+    serveEpc(server, graph, cache)
 
 proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
   var p = parseopt.initOptParser(cmd)
@@ -464,7 +460,9 @@ proc handleCmdLine(cache: IdentCache) =
     extccomp.initVars()
     processCmdLine(passCmd2, "")
 
-    mainCommand(cache)
+    let graph = newModuleGraph()
+    graph.suggestMode = true
+    mainCommand(graph, cache)
 
 when false:
   proc quitCalled() {.noconv.} =
diff --git a/tools/nimsuggest/tester.nim b/tools/nimsuggest/tester.nim
index 70a7da5b2..c90afe3db 100644
--- a/tools/nimsuggest/tester.nim
+++ b/tools/nimsuggest/tester.nim
@@ -7,27 +7,31 @@ import os, osproc, strutils, streams, re
 
 type
   Test = object
-    cmd: string
+    cmd, dest: string
+    startup: seq[string]
     script: seq[(string, string)]
 
 const
-  curDir = when defined(windows): "" else: "./"
+  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)
-  let dest = getTempDir() / extractFilename(filename)
-  result.cmd = nimsug & " --tester " & dest
+  result.dest = getTempDir() / extractFilename(filename)
+  result.cmd = nimsug & " --tester " & result.dest
   result.script = @[]
-  var tmp = open(dest, fmWrite)
+  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 & ";" & dest & ":" & $i & ":" & $marker
+      markers.add "\"" & filename & "\";\"" & result.dest & "\":" & $i & ":" & $marker
       tmp.writeLine x.replace(cursorMarker, "")
     else:
       tmp.writeLine x
@@ -36,26 +40,104 @@ proc parseTest(filename: string): Test =
     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), ""))
-      else:
+        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,
@@ -69,17 +151,18 @@ proc runTest(filename: string): int =
     while outp.readLine(a):
       if a == DummyEof: break
     for req, resp in items(s.script):
-      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
+      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()
@@ -90,7 +173,7 @@ proc runTest(filename: string): int =
 
 proc main() =
   var failures = 0
-  for x in walkFiles("tests/t*.nim"):
+  for x in walkFiles(getAppDir() / "tests/t*.nim"):
     echo "Test ", x
     failures += runTest(expandFilename(x))
   if failures > 0:
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/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
index 667c1660c..f5cda9505 100644
--- a/tools/nimsuggest/tests/tstrutils.nim
+++ b/tools/nimsuggest/tests/tstrutils.nim
@@ -1,6 +1,6 @@
 discard """
-$nimsuggest --tester ../nim/lib/pure/strutils.nim
->def ../nim/lib/pure/strutils.nim:2300:6
+$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
 """
 
diff --git a/web/news/e029_version_0_16_0.rst b/web/news/e029_version_0_16_0.rst
index 2f6c72c82..94c9757a7 100644
--- a/web/news/e029_version_0_16_0.rst
+++ b/web/news/e029_version_0_16_0.rst
@@ -26,7 +26,9 @@ 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
 -----------------