summary refs log tree commit diff stats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/finish.nim167
-rw-r--r--tools/niminst/buildbat.tmpl6
-rw-r--r--tools/niminst/buildsh.tmpl7
-rw-r--r--tools/niminst/debcreation.nim4
-rw-r--r--tools/niminst/deinstall.tmpl2
-rw-r--r--tools/niminst/install.tmpl2
-rw-r--r--tools/niminst/makefile.tmpl4
-rw-r--r--tools/niminst/niminst.nim59
-rw-r--r--tools/niminst/nsis.tmpl2
-rw-r--r--tools/nimsuggest/nimsuggest.nim475
-rw-r--r--tools/nimsuggest/nimsuggest.nim.cfg16
-rw-r--r--tools/nimsuggest/nimsuggest.nimble11
-rw-r--r--tools/nimsuggest/sexp.nim697
-rw-r--r--tools/nimsuggest/tester.nim182
-rw-r--r--tools/nimsuggest/tests/dep_v1.nim8
-rw-r--r--tools/nimsuggest/tests/dep_v2.nim9
-rw-r--r--tools/nimsuggest/tests/tdef1.nim16
-rw-r--r--tools/nimsuggest/tests/tdot1.nim14
-rw-r--r--tools/nimsuggest/tests/tdot2.nim29
-rw-r--r--tools/nimsuggest/tests/tdot3.nim27
-rw-r--r--tools/nimsuggest/tests/tinclude.nim7
-rw-r--r--tools/nimsuggest/tests/tstrutils.nim9
-rw-r--r--tools/nimweb.nim36
-rw-r--r--tools/noprefix.nim32
-rw-r--r--tools/start.bat8
-rw-r--r--tools/vccenv/vccenv.nim58
-rw-r--r--tools/vccenv/vccexe.nim66
-rw-r--r--tools/website.tmpl8
28 files changed, 1877 insertions, 84 deletions
diff --git a/tools/finish.nim b/tools/finish.nim
new file mode 100644
index 000000000..cac001d79
--- /dev/null
+++ b/tools/finish.nim
@@ -0,0 +1,167 @@
+
+# -------------- post unzip steps ---------------------------------------------
+
+import strutils, os, osproc
+
+when defined(windows):
+  import registry
+
+  proc askBool(m: string): bool =
+    stdout.write m
+    while true:
+      let answer = stdin.readLine().normalize
+      case answer
+      of "y", "yes":
+        return true
+      of "n", "no":
+        return false
+      else:
+        echo "Please type 'y' or 'n'"
+
+  proc askNumber(m: string; a, b: int): int =
+    stdout.write m
+    stdout.write " [" & $a & ".." & $b & "] "
+    while true:
+      let answer = stdin.readLine()
+      try:
+        result = parseInt answer
+        if result < a or result > b:
+          raise newException(ValueError, "number out of range")
+        break
+      except ValueError:
+        echo "Please type in a number between ", a, " and ", b
+
+  proc patchConfig(mingw: string) =
+    const
+      cfgFile = "config/nim.cfg"
+      lookFor = """#gcc.path = r"$nim\dist\mingw\bin""""
+      replacePattern = """gcc.path = r"$1""""
+    try:
+      let cfg = readFile(cfgFile)
+      let newCfg = cfg.replace(lookFor, replacePattern % mingw)
+      if newCfg == cfg:
+        echo "Could not patch 'config/nim.cfg' [Error]"
+        echo "Reason: patch substring not found:"
+        echo lookFor
+      else:
+        writeFile(cfgFile, newCfg)
+    except IOError:
+      echo "Could not access 'config/nim.cfg' [Error]"
+
+  proc addToPathEnv(e: string) =
+    let p = getUnicodeValue(r"Environment", "Path", HKEY_CURRENT_USER)
+    let x = if e.contains(Whitespace): "\"" & e & "\"" else: e
+    setUnicodeValue(r"Environment", "Path", p & ";" & x, HKEY_CURRENT_USER)
+
+  proc createShortcut(src, dest: string; icon = "") =
+    var cmd = "bin\\makelink.exe \"" & src & "\" \"\" \"" & dest &
+         ".lnk\" \"\" 1 \"" & splitFile(src).dir & "\""
+    if icon.len != 0:
+      cmd.add " \"" & icon & "\" 0"
+    discard execShellCmd(cmd)
+
+  proc createStartMenuEntry() =
+    let appdata = getEnv("APPDATA")
+    if appdata.len == 0: return
+    let dest = appdata & r"\Microsoft\Windows\Start Menu\Programs\Nim-" &
+               NimVersion
+    if dirExists(dest): return
+    if askBool("Would like to add Nim-" & NimVersion &
+               " to your start menu? (y/n) "):
+      createDir(dest)
+      createShortcut(getCurrentDir() / "tools" / "start.bat", dest / "Nim",
+                     getCurrentDir() / r"icons\nim.ico")
+      if fileExists("doc/overview.html"):
+        createShortcut(getCurrentDir() / "doc" / "html" / "overview.html",
+                       dest / "Overview")
+      if dirExists(r"dist\aporia-0.4.0"):
+        createShortcut(getCurrentDir() / r"dist\aporia-0.4.0\bin\aporia.exe",
+                       dest / "Aporia")
+
+  proc checkGccArch(mingw: string): bool =
+    let gccExe = mingw / r"gcc.exe"
+    if fileExists(gccExe):
+      try:
+        let arch = execProcess(gccExe, ["-dumpmachine"], nil, {poStdErrToStdOut,
+                                                               poUsePath})
+        when hostCPU == "i386":
+          result = arch.startsWith("i686-")
+        elif hostCPU == "amd64":
+          result = arch.startsWith("x86_64-")
+        else:
+          {.error: "Unknown CPU for Windows.".}
+      except OSError, IOError:
+        result = false
+
+  proc tryDirs(incompat: var seq[string]; dirs: varargs[string]): string =
+    let bits = $(sizeof(pointer)*8)
+    for d in dirs:
+      if dirExists d:
+        let x = expandFilename(d / "bin")
+        if checkGccArch(x): return x
+        else: incompat.add x
+      elif dirExists(d & bits):
+        let x = expandFilename((d & bits) / "bin")
+        if checkGccArch(x): return x
+        else: incompat.add x
+
+proc main() =
+  when defined(windows):
+    let desiredPath = expandFilename(getCurrentDir() / "bin")
+    let p = getUnicodeValue(r"Environment", "Path",
+      HKEY_CURRENT_USER)
+    var alreadyInPath = false
+    var mingWchoices: seq[string] = @[]
+    var incompat: seq[string] = @[]
+    for x in p.split(';'):
+      if x.len == 0: continue
+      let y = try: expandFilename(if x[0] == '"' and x[^1] == '"':
+                                    substr(x, 1, x.len-2) else: x)
+              except: ""
+      if y == desiredPath: alreadyInPath = true
+      if y.toLowerAscii.contains("mingw"):
+        if dirExists(y):
+          if checkGccArch(y): mingWchoices.add y
+          else: incompat.add y
+
+    if alreadyInPath:
+      echo "bin/nim.exe is already in your PATH [Skipping]"
+    else:
+      if askBool("nim.exe is not in your PATH environment variable.\n" &
+          "Should it be added permanently? (y/n) "):
+        addToPathEnv(desiredPath)
+    if mingWchoices.len == 0:
+      # No mingw in path, so try a few locations:
+      let alternative = tryDirs(incompat, "dist/mingw", "../mingw", r"C:\mingw")
+      if alternative.len == 0:
+        if incompat.len > 0:
+          echo "The following *incompatible* MingW installations exist"
+          for x in incompat: echo x
+        echo "No compatible MingW candidates found " &
+             "in the standard locations [Error]"
+      else:
+        if askBool("Found a MingW directory that is not in your PATH.\n" &
+                   alternative &
+                   "\nShould it be added to your PATH permanently? (y/n) "):
+          addToPathEnv(alternative)
+        elif askBool("Do you want to patch Nim's config to use this? (y/n) "):
+          patchConfig(alternative)
+    elif mingWchoices.len == 1:
+      if askBool("MingW installation found at " & mingWchoices[0] & "\n" &
+         "Do you want to patch Nim's config to use this?\n" &
+         "(Not required since it's in your PATH!) (y/n) "):
+        patchConfig(mingWchoices[0])
+    else:
+      echo "Multiple MingW installations found: "
+      for i in 0..high(mingWchoices):
+        echo "[", i, "] ", mingWchoices[i]
+      if askBool("Do you want to patch Nim's config to use one of these? (y/n) "):
+        let idx = askNumber("Which one do you want to use for Nim? ",
+            1, len(mingWchoices))
+        patchConfig(mingWchoices[idx-1])
+    createStartMenuEntry()
+  else:
+    echo("Add ", getCurrentDir(), "/bin to your PATH...")
+
+when isMainModule:
+  main()
diff --git a/tools/niminst/buildbat.tmpl b/tools/niminst/buildbat.tmpl
index 2a8da144d..278b6caea 100644
--- a/tools/niminst/buildbat.tmpl
+++ b/tools/niminst/buildbat.tmpl
@@ -1,5 +1,5 @@
 #? stdtmpl(subsChar='?') | standard
-#proc generateBuildBatchScript(c: ConfigData, winIndex, cpuIndex: int): string = 
+#proc generateBuildBatchScript(c: ConfigData, winIndex, cpuIndex: int): string =
 #  result = "@echo off\nREM Generated by niminst\n"
 SET CC=gcc
 SET LINKER=gcc
@@ -23,8 +23,8 @@ CALL %CC% %COMP_FLAGS% -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")}
 IF ERRORLEVEL 1 (GOTO:END)
 #    end for
 
-ECHO %LINKER% -o ?{"%BIN_DIR%"\toLower(c.name)}.exe ?linkCmd %LINK_FLAGS%
-CALL %LINKER% -o ?{"%BIN_DIR%"\toLower(c.name)}.exe ?linkCmd %LINK_FLAGS%
+ECHO %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS%
+CALL %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS%
 
 #  end block
 
diff --git a/tools/niminst/buildsh.tmpl b/tools/niminst/buildsh.tmpl
index 220ecdb7f..c571f4d66 100644
--- a/tools/niminst/buildsh.tmpl
+++ b/tools/niminst/buildsh.tmpl
@@ -53,7 +53,7 @@ uos=`echo $uos | tr "[:upper:]" "[:lower:]"`
 case $uos in
   *linux* )
     myos="linux"
-    LINK_FLAGS="$LINK_FLAGS -ldl -lm"
+    LINK_FLAGS="$LINK_FLAGS -ldl -lm -lrt"
     ;;
   *dragonfly* )
     myos="freebsd"
@@ -94,6 +94,9 @@ case $uos in
     myos="haiku"
     LINK_FLAGS="$LINK_FLAGS -lroot -lnetwork"
     ;;
+  *mingw* )
+    myos="windows"
+    ;;
   *)
     echo 2>&1 "Error: unknown operating system: $uos"
     exit 1
@@ -144,7 +147,7 @@ case $myos in
     $CC $COMP_FLAGS -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")}
 #        add(linkCmd, " \\\n" & changeFileExt(f, "o"))
 #      end for
-    $LINKER -o ?{"$binDir/" & toLower(c.name)} ?linkCmd $LINK_FLAGS
+    $LINKER -o ?{"$binDir/" & toLowerAscii(c.name)} ?linkCmd $LINK_FLAGS
     ;;
 #    end for
   *)
diff --git a/tools/niminst/debcreation.nim b/tools/niminst/debcreation.nim
index 36b2a29ec..0ecea132f 100644
--- a/tools/niminst/debcreation.nim
+++ b/tools/niminst/debcreation.nim
@@ -148,7 +148,7 @@ proc prepDeb*(packName, version, mtnName, mtnEmail, shortDesc, desc: string,
   ## binaries/config/docs/lib: files relative to nim's root, that need to
   ##   be installed.
 
-  let pkgName = packName.toLower()
+  let pkgName = packName.toLowerAscii()
 
   var workingDir = getTempDir() / "niminst" / "deb"
   var upstreamSource = (pkgName & "-" & version)
@@ -168,7 +168,7 @@ proc prepDeb*(packName, version, mtnName, mtnEmail, shortDesc, desc: string,
   echo("Creating necessary files in debian/")
   createDir(workingDir / upstreamSource / "debian")
 
-  template writeDebian(f, s: string): expr =
+  template writeDebian(f, s: string) =
     writeFile(workingDir / upstreamSource / "debian" / f, s)
 
   var controlFile = createControl(pkgName, makeMtn(mtnName, mtnEmail),
diff --git a/tools/niminst/deinstall.tmpl b/tools/niminst/deinstall.tmpl
index 3cdfbf45d..bba310f76 100644
--- a/tools/niminst/deinstall.tmpl
+++ b/tools/niminst/deinstall.tmpl
@@ -1,7 +1,7 @@
 #? stdtmpl(subsChar='?') | standard
 #proc generateDeinstallScript(c: ConfigData): string =
 #  result = "#! /bin/sh\n# Generated by niminst\n"
-#  var proj = c.name.toLower
+#  var proj = c.name.toLowerAscii
 
 if [ $# -eq 1 ] ; then
   case $1 in
diff --git a/tools/niminst/install.tmpl b/tools/niminst/install.tmpl
index 3f17840a8..d72b132ef 100644
--- a/tools/niminst/install.tmpl
+++ b/tools/niminst/install.tmpl
@@ -1,7 +1,7 @@
 #? stdtmpl(subsChar = '?') | standard
 #proc generateInstallScript(c: ConfigData): string =
 #  result = "#! /bin/sh\n# Generated by niminst\n"
-#  var proj = c.name.toLower
+#  var proj = c.name.toLowerAscii
 
 ## Current directory you start script from
 BASE_DIR=$(pwd)
diff --git a/tools/niminst/makefile.tmpl b/tools/niminst/makefile.tmpl
index 5c95ccda9..ce2db1c48 100644
--- a/tools/niminst/makefile.tmpl
+++ b/tools/niminst/makefile.tmpl
@@ -157,7 +157,7 @@ endif
 %.o: %.c
 	$(CC) $(COMP_FLAGS) -Ic_code -c $< -o $@
 
-?{"$(binDir)/" & toLower(c.name)}: $(oFiles)
+?{"$(binDir)/" & toLowerAscii(c.name)}: $(oFiles)
 	@mkdir -p $(binDir)
 	$(LINKER) -o $@ $^ $(LINK_FLAGS)
 	@echo "SUCCESS"
@@ -165,4 +165,4 @@ endif
 .PHONY: clean
 
 clean:
-	rm -f $(oFiles) ?{"$(binDir)/" & toLower(c.name)}
+	rm -f $(oFiles) ?{"$(binDir)/" & toLowerAscii(c.name)}
diff --git a/tools/niminst/niminst.nim b/tools/niminst/niminst.nim
index b63849a10..e0b8ad9b3 100644
--- a/tools/niminst/niminst.nim
+++ b/tools/niminst/niminst.nim
@@ -313,7 +313,7 @@ proc parseIniFile(c: var ConfigData) =
       of cfgSectionStart:
         section = normalize(k.section)
       of cfgKeyValuePair:
-        var v = k.value % c.vars
+        var v = `%`(k.value, c.vars, {useEnvironment, useEmpty})
         c.vars[k.key] = v
 
         case section
@@ -552,7 +552,7 @@ proc srcdist(c: var ConfigData) =
 # --------------------- generate inno setup -----------------------------------
 proc setupDist(c: var ConfigData) =
   let scrpt = generateInnoSetup(c)
-  let n = "build" / "install_$#_$#.iss" % [toLower(c.name), c.version]
+  let n = "build" / "install_$#_$#.iss" % [toLowerAscii(c.name), c.version]
   writeFile(n, scrpt, "\13\10")
   when defined(windows):
     if c.innosetup.path.len == 0:
@@ -569,7 +569,7 @@ proc setupDist(c: var ConfigData) =
 # --------------------- generate NSIS setup -----------------------------------
 proc setupDist2(c: var ConfigData) =
   let scrpt = generateNsisSetup(c)
-  let n = "build" / "install_$#_$#.nsi" % [toLower(c.name), c.version]
+  let n = "build" / "install_$#_$#.nsi" % [toLowerAscii(c.name), c.version]
   writeFile(n, scrpt, "\13\10")
   when defined(windows):
     if c.nsisSetup.path.len == 0:
@@ -586,7 +586,7 @@ proc setupDist2(c: var ConfigData) =
 # ------------------ generate ZIP file ---------------------------------------
 when haveZipLib:
   proc zipDist(c: var ConfigData) =
-    var proj = toLower(c.name) & "-" & c.version
+    var proj = toLowerAscii(c.name) & "-" & c.version
     var n = "$#.zip" % proj
     if c.outdir.len == 0: n = "build" / n
     else: n = c.outdir / n
@@ -618,40 +618,49 @@ when haveZipLib:
       quit("Cannot open for writing: " & n)
 
 proc xzDist(c: var ConfigData; windowsZip=false) =
-  let proj = toLower(c.name) & "-" & c.version
+  let proj = toLowerAscii(c.name) & "-" & c.version
   let tmpDir = if c.outdir.len == 0: "build" else: c.outdir
 
-  template processFile(z, dest, src) =
-    let s = src
-    let d = dest
-    echo "Copying ", s, " to ", tmpDir / d
-    let destdir = tmpdir / d.splitFile.dir
-    if not dirExists(destdir): createDir(destdir)
-    copyFileWithPermissions(s, tmpDir / d)
-
-  processFile(z, proj / buildBatFile32, "build" / buildBatFile32)
-  processFile(z, proj / buildBatFile64, "build" / buildBatFile64)
-  processFile(z, proj / buildShFile, "build" / buildShFile)
-  processFile(z, proj / makeFile, "build" / makeFile)
-  processFile(z, proj / installShFile, installShFile)
-  processFile(z, proj / deinstallShFile, deinstallShFile)
+  proc processFile(destFile, src: string) =
+    let dest = tmpDir / destFile
+    echo "Copying ", src, " to ", dest
+    if not existsFile(src):
+      echo "[Warning] Source file doesn't exist: ", src
+    let destDir = dest.splitFile.dir
+    if not dirExists(destDir): createDir(destDir)
+    copyFileWithPermissions(src, dest)
+
+  if not windowsZip and not existsFile("build" / buildBatFile32):
+    quit("No C sources found in ./build/, please build by running " &
+         "./koch csource -d:release.")
+
   if not windowsZip:
+    processFile(proj / buildBatFile32, "build" / buildBatFile32)
+    processFile(proj / buildBatFile64, "build" / buildBatFile64)
+    processFile(proj / buildShFile, "build" / buildShFile)
+    processFile(proj / makeFile, "build" / makeFile)
+    processFile(proj / installShFile, installShFile)
+    processFile(proj / deinstallShFile, deinstallShFile)
     for f in walkFiles(c.libpath / "lib/*.h"):
-      processFile(z, proj / "c_code" / extractFilename(f), f)
+      processFile(proj / "c_code" / extractFilename(f), f)
     for osA in 1..c.oses.len:
       for cpuA in 1..c.cpus.len:
         var dir = buildDir(osA, cpuA)
         for k, f in walkDir("build" / dir):
-          if k == pcFile: processFile(z, proj / dir / extractFilename(f), f)
+          if k == pcFile: processFile(proj / dir / extractFilename(f), f)
+  else:
+    for f in items(c.cat[fcWinBin]):
+      let filename = f.extractFilename
+      processFile(proj / "bin" / filename, f)
 
   let osSpecific = if windowsZip: fcWindows else: fcUnix
   for cat in items({fcConfig..fcOther, osSpecific, fcNimble}):
     echo("Current category: ", cat)
-    for f in items(c.cat[cat]): processFile(z, proj / f, f)
+    for f in items(c.cat[cat]): processFile(proj / f, f)
 
   # Copy the .nimble file over
   let nimbleFile = c.nimblePkgName & ".nimble"
-  processFile(z, proj / nimbleFile, nimbleFile)
+  processFile(proj / nimbleFile, nimbleFile)
 
   when true:
     let oldDir = getCurrentDir()
@@ -683,11 +692,11 @@ proc debDist(c: var ConfigData) =
   echo("Copying source to tmp/niminst/deb/")
   var currentSource = getCurrentDir()
   var workingDir = getTempDir() / "niminst" / "deb"
-  var upstreamSource = (c.name.toLower() & "-" & c.version)
+  var upstreamSource = (c.name.toLowerAscii() & "-" & c.version)
 
   createDir(workingDir / upstreamSource)
 
-  template copyNimDist(f, dest: string): stmt =
+  template copyNimDist(f, dest: string) =
     createDir((workingDir / upstreamSource / dest).splitFile.dir)
     copyFile(currentSource / f, workingDir / upstreamSource / dest)
 
diff --git a/tools/niminst/nsis.tmpl b/tools/niminst/nsis.tmpl
index 639a01b6b..95d4652e3 100644
--- a/tools/niminst/nsis.tmpl
+++ b/tools/niminst/nsis.tmpl
@@ -202,7 +202,7 @@
     ; Shortcuts
     #  if d.len >= 6:
     #    let startMenuEntry = d[5]
-    #    let e = splitFile(startMenuEntry).name.capitalize
+    #    let e = splitFile(startMenuEntry).name.capitalizeAscii
       !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
         CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\?{e}.lnk" "$INSTDIR\?dir\?{startMenuEntry.toWin}"
       !insertmacro MUI_STARTMENU_WRITE_END
diff --git a/tools/nimsuggest/nimsuggest.nim b/tools/nimsuggest/nimsuggest.nim
new file mode 100644
index 000000000..b5e7b282f
--- /dev/null
+++ b/tools/nimsuggest/nimsuggest.nim
@@ -0,0 +1,475 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2016 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Nimsuggest is a tool that helps to give editors IDE like capabilities.
+
+import strutils, os, parseopt, parseutils, sequtils, net, rdstdin, sexp
+# Do NOT import suggest. It will lead to wierd bugs with
+# suggestionResultHook, because suggest.nim is included by sigmatch.
+# So we import that one instead.
+import compiler/options, compiler/commands, compiler/modules, compiler/sem,
+  compiler/passes, compiler/passaux, compiler/msgs, compiler/nimconf,
+  compiler/extccomp, compiler/condsyms, compiler/lists,
+  compiler/sigmatch, compiler/ast, compiler/scriptconfig,
+  compiler/idents, compiler/modulegraphs
+
+when defined(windows):
+  import winlean
+else:
+  import posix
+
+const DummyEof = "!EOF!"
+const Usage = """
+Nimsuggest - Tool to give every editor IDE like capabilities for Nim
+Usage:
+  nimsuggest [options] projectfile.nim
+
+Options:
+  --port:PORT             port, by default 6000
+  --address:HOST          binds to that address, by default ""
+  --stdin                 read commands from stdin and write results to
+                          stdout instead of using sockets
+  --epc                   use emacs epc mode
+  --debug                 enable debug output
+  --log                   enable verbose logging to nimsuggest.log file
+  --v2                    use version 2 of the protocol; more features and
+                          much faster
+  --tester                implies --v2 and --stdin and outputs a line
+                          '""" & DummyEof & """' for the tester
+
+The server then listens to the connection and takes line-based commands.
+
+In addition, all command line options of Nim that do not affect code generation
+are supported.
+"""
+type
+  Mode = enum mstdin, mtcp, mepc
+
+var
+  gPort = 6000.Port
+  gAddress = ""
+  gMode: Mode
+  gEmitEof: bool # whether we write '!EOF!' dummy lines
+  gLogging = false
+
+const
+  seps = {':', ';', ' ', '\t'}
+  Help = "usage: sug|con|def|use|dus|chk|mod|highlight|outline file.nim[;dirtyfile.nim]:line:col\n" &
+         "type 'quit' to quit\n" &
+         "type 'debug' to toggle debug mode on/off\n" &
+         "type 'terse' to toggle terse mode on/off"
+
+type
+  EUnexpectedCommand = object of Exception
+
+proc logStr(line: string) =
+  var f: File
+  if open(f, getHomeDir() / "nimsuggest.log", fmAppend):
+    f.writeLine(line)
+    f.close()
+
+proc parseQuoted(cmd: string; outp: var string; start: int): int =
+  var i = start
+  i += skipWhitespace(cmd, i)
+  if cmd[i] == '"':
+    i += parseUntil(cmd, outp, '"', i+1)+2
+  else:
+    i += parseUntil(cmd, outp, seps, i)
+  result = i
+
+proc sexp(s: IdeCmd|TSymKind): SexpNode = sexp($s)
+
+proc sexp(s: Suggest): SexpNode =
+  # If you change the order here, make sure to change it over in
+  # nim-mode.el too.
+  result = convertSexp([
+    s.section,
+    s.symkind,
+    s.qualifiedPath.map(newSString),
+    s.filePath,
+    s.forth,
+    s.line,
+    s.column,
+    s.doc
+  ])
+
+proc sexp(s: seq[Suggest]): SexpNode =
+  result = newSList()
+  for sug in s:
+    result.add(sexp(sug))
+
+proc listEpc(): SexpNode =
+  # This function is called from Emacs to show available options.
+  let
+    argspecs = sexp("file line column dirtyfile".split(" ").map(newSSymbol))
+    docstring = sexp("line starts at 1, column at 0, dirtyfile is optional")
+  result = newSList()
+  for command in ["sug", "con", "def", "use", "dus", "chk", "mod"]:
+    let
+      cmd = sexp(command)
+      methodDesc = newSList()
+    methodDesc.add(cmd)
+    methodDesc.add(argspecs)
+    methodDesc.add(docstring)
+    result.add(methodDesc)
+
+proc findNode(n: PNode): PSym =
+  #echo "checking node ", n.info
+  if n.kind == nkSym:
+    if isTracked(n.info, n.sym.name.s.len): return n.sym
+  else:
+    for i in 0 ..< safeLen(n):
+      let res = n.sons[i].findNode
+      if res != nil: return res
+
+proc symFromInfo(graph: ModuleGraph; gTrackPos: TLineInfo): PSym =
+  let m = graph.getModule(gTrackPos.fileIndex)
+  #echo m.isNil, " I knew it ", gTrackPos.fileIndex
+  if m != nil and m.ast != nil:
+    result = m.ast.findNode
+
+proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int;
+             graph: ModuleGraph; cache: IdentCache) =
+  if gLogging:
+    logStr("cmd: " & $cmd & ", file: " & file & ", dirtyFile: " & dirtyfile & "[" & $line & ":" & $col & "]")
+  gIdeCmd = cmd
+  if cmd == ideUse and suggestVersion != 2:
+    graph.resetAllModules()
+  var isKnownFile = true
+  let dirtyIdx = file.fileInfoIdx(isKnownFile)
+
+  if dirtyfile.len != 0: msgs.setDirtyFile(dirtyIdx, dirtyfile)
+  else: msgs.setDirtyFile(dirtyIdx, nil)
+
+  gTrackPos = newLineInfo(dirtyIdx, line, col)
+  gErrorCounter = 0
+  if suggestVersion < 2:
+    usageSym = nil
+  if not isKnownFile:
+    graph.compileProject(cache)
+  if suggestVersion == 2 and gIdeCmd in {ideUse, ideDus} and
+      dirtyfile.len == 0:
+    discard "no need to recompile anything"
+  else:
+    let modIdx = graph.parentModule(dirtyIdx)
+    graph.markDirty dirtyIdx
+    graph.markClientsDirty dirtyIdx
+    if gIdeCmd != ideMod:
+      graph.compileProject(cache, modIdx)
+  if gIdeCmd in {ideUse, ideDus}:
+    let u = if suggestVersion >= 2: graph.symFromInfo(gTrackPos) else: usageSym
+    if u != nil:
+      listUsages(u)
+    else:
+      localError(gTrackPos, "found no symbol at this position " & $gTrackPos)
+
+proc executeEpc(cmd: IdeCmd, args: SexpNode;
+                graph: ModuleGraph; cache: IdentCache) =
+  let
+    file = args[0].getStr
+    line = args[1].getNum
+    column = args[2].getNum
+  var dirtyfile = ""
+  if len(args) > 3:
+    dirtyfile = args[3].getStr(nil)
+  execute(cmd, file, dirtyfile, int(line), int(column), graph, cache)
+
+proc returnEpc(socket: var Socket, uid: BiggestInt, s: SexpNode|string,
+               return_symbol = "return") =
+  let response = $convertSexp([newSSymbol(return_symbol), uid, s])
+  socket.send(toHex(len(response), 6))
+  socket.send(response)
+
+template sendEpc(results: typed, tdef, hook: untyped) =
+  hook = proc (s: tdef) =
+    results.add(
+      # Put newlines to parse output by flycheck-nim.el
+      when results is string: s & "\n"
+      else: s
+    )
+
+  executeEpc(gIdeCmd, args, graph, cache)
+  let res = sexp(results)
+  if gLogging:
+    logStr($res)
+  returnEPC(client, uid, res)
+
+template checkSanity(client, sizeHex, size, messageBuffer: typed) =
+  if client.recv(sizeHex, 6) != 6:
+    raise newException(ValueError, "didn't get all the hexbytes")
+  if parseHex(sizeHex, size) == 0:
+    raise newException(ValueError, "invalid size hex: " & $sizeHex)
+  if client.recv(messageBuffer, size) != size:
+    raise newException(ValueError, "didn't get all the bytes")
+
+template setVerbosity(level: typed) =
+  gVerbosity = level
+  gNotes = NotesVerbosity[gVerbosity]
+
+proc connectToNextFreePort(server: Socket, host: string): Port =
+  server.bindaddr(Port(0), host)
+  let (_, port) = server.getLocalAddr
+  result = port
+
+proc parseCmdLine(cmd: string; graph: ModuleGraph; cache: IdentCache) =
+  template toggle(sw) =
+    if sw in gGlobalOptions:
+      excl(gGlobalOptions, sw)
+    else:
+      incl(gGlobalOptions, sw)
+    return
+
+  template err() =
+    echo Help
+    return
+
+  var opc = ""
+  var i = parseIdent(cmd, opc, 0)
+  case opc.normalize
+  of "sug": gIdeCmd = ideSug
+  of "con": gIdeCmd = ideCon
+  of "def": gIdeCmd = ideDef
+  of "use": gIdeCmd = ideUse
+  of "dus": gIdeCmd = ideDus
+  of "mod": gIdeCmd = ideMod
+  of "chk":
+    gIdeCmd = ideChk
+    incl(gGlobalOptions, optIdeDebug)
+  of "highlight": gIdeCmd = ideHighlight
+  of "outline": gIdeCmd = ideOutline
+  of "quit": quit()
+  of "debug": toggle optIdeDebug
+  of "terse": toggle optIdeTerse
+  else: err()
+  var dirtyfile = ""
+  var orig = ""
+  i = parseQuoted(cmd, orig, i)
+  if cmd[i] == ';':
+    i = parseQuoted(cmd, dirtyfile, i+1)
+  i += skipWhile(cmd, seps, i)
+  var line = -1
+  var col = 0
+  i += parseInt(cmd, line, i)
+  i += skipWhile(cmd, seps, i)
+  i += parseInt(cmd, col, i)
+
+  execute(gIdeCmd, orig, dirtyfile, line, col-1, graph, cache)
+
+proc serveStdin(graph: ModuleGraph; cache: IdentCache) =
+  if gEmitEof:
+    echo DummyEof
+    while true:
+      let line = readLine(stdin)
+      parseCmdLine line, graph, cache
+      echo DummyEof
+      flushFile(stdout)
+  else:
+    echo Help
+    var line = ""
+    while readLineFromStdin("> ", line):
+      parseCmdLine line, graph, cache
+      echo ""
+      flushFile(stdout)
+
+proc serveTcp(graph: ModuleGraph; cache: IdentCache) =
+  var server = newSocket()
+  server.bindAddr(gPort, gAddress)
+  var inp = "".TaintedString
+  server.listen()
+
+  while true:
+    var stdoutSocket = newSocket()
+    msgs.writelnHook = proc (line: string) =
+      stdoutSocket.send(line & "\c\L")
+
+    accept(server, stdoutSocket)
+
+    stdoutSocket.readLine(inp)
+    parseCmdLine inp.string, graph, cache
+
+    stdoutSocket.send("\c\L")
+    stdoutSocket.close()
+
+proc serveEpc(server: Socket; graph: ModuleGraph; cache: IdentCache) =
+  var client = newSocket()
+  # Wait for connection
+  accept(server, client)
+  if gLogging:
+    var it = searchPaths.head
+    while it != nil:
+      logStr(PStrEntry(it).data)
+      it = it.next
+    msgs.writelnHook = proc (line: string) = logStr(line)
+
+  while true:
+    var
+      sizeHex = ""
+      size = 0
+      messageBuffer = ""
+    checkSanity(client, sizeHex, size, messageBuffer)
+    let
+      message = parseSexp($messageBuffer)
+      epcAPI = message[0].getSymbol
+    case epcAPI:
+    of "call":
+      let
+        uid = message[1].getNum
+        args = message[3]
+
+      gIdeCmd = parseIdeCmd(message[2].getSymbol)
+      case gIdeCmd
+      of ideChk:
+        setVerbosity(1)
+        # Use full path because other emacs plugins depends it
+        gListFullPaths = true
+        incl(gGlobalOptions, optIdeDebug)
+        var hints_or_errors = ""
+        sendEpc(hints_or_errors, string, msgs.writelnHook)
+      of ideSug, ideCon, ideDef, ideUse, ideDus, ideOutline, ideHighlight:
+        setVerbosity(0)
+        var suggests: seq[Suggest] = @[]
+        sendEpc(suggests, Suggest, suggestionResultHook)
+      else: discard
+    of "methods":
+      returnEpc(client, message[1].getNum, listEPC())
+    of "epc-error":
+      stderr.writeline("recieved epc error: " & $messageBuffer)
+      raise newException(IOError, "epc error")
+    else:
+      let errMessage = case epcAPI
+                       of "return", "return-error":
+                         "no return expected"
+                       else:
+                         "unexpected call: " & epcAPI
+      raise newException(EUnexpectedCommand, errMessage)
+
+proc mainCommand(graph: ModuleGraph; cache: IdentCache) =
+  clearPasses()
+  registerPass verbosePass
+  registerPass semPass
+  gCmd = cmdIdeTools
+  incl gGlobalOptions, optCaasEnabled
+  isServing = true
+  wantMainModule()
+  appendStr(searchPaths, options.libpath)
+  #if gProjectFull.len != 0:
+    # current path is always looked first for modules
+  #  prependStr(searchPaths, gProjectPath)
+
+  # do not stop after the first error:
+  msgs.gErrorMax = high(int)
+
+  case gMode
+  of mstdin:
+    compileProject(graph, cache)
+    #modules.gFuzzyGraphChecking = false
+    serveStdin(graph, cache)
+  of mtcp:
+    # until somebody accepted the connection, produce no output (logging is too
+    # slow for big projects):
+    msgs.writelnHook = proc (msg: string) = discard
+    compileProject(graph, cache)
+    #modules.gFuzzyGraphChecking = false
+    serveTcp(graph, cache)
+  of mepc:
+    var server = newSocket()
+    let port = connectToNextFreePort(server, "localhost")
+    server.listen()
+    echo port
+    compileProject(graph, cache)
+    serveEpc(server, graph, cache)
+
+proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
+  var p = parseopt.initOptParser(cmd)
+  while true:
+    parseopt.next(p)
+    case p.kind
+    of cmdEnd: break
+    of cmdLongoption, cmdShortOption:
+      case p.key.normalize
+      of "port":
+        gPort = parseInt(p.val).Port
+        gMode = mtcp
+      of "address":
+        gAddress = p.val
+        gMode = mtcp
+      of "stdin": gMode = mstdin
+      of "epc":
+        gMode = mepc
+        gVerbosity = 0          # Port number gotta be first.
+      of "debug":
+        incl(gGlobalOptions, optIdeDebug)
+      of "v2":
+        suggestVersion = 2
+      of "tester":
+        suggestVersion = 2
+        gMode = mstdin
+        gEmitEof = true
+      of "log":
+        gLogging = true
+      else: processSwitch(pass, p)
+    of cmdArgument:
+      options.gProjectName = unixToNativePath(p.key)
+      # if processArgument(pass, p, argsCount): break
+
+proc handleCmdLine(cache: IdentCache) =
+  if paramCount() == 0:
+    stdout.writeline(Usage)
+  else:
+    processCmdLine(passCmd1, "")
+    if gMode != mstdin:
+      msgs.writelnHook = proc (msg: string) = discard
+    if gProjectName != "":
+      try:
+        gProjectFull = canonicalizePath(gProjectName)
+      except OSError:
+        gProjectFull = gProjectName
+      var p = splitFile(gProjectFull)
+      gProjectPath = canonicalizePath p.dir
+      gProjectName = p.name
+    else:
+      gProjectPath = canonicalizePath getCurrentDir()
+
+    # Find Nim's prefix dir.
+    let binaryPath = findExe("nim")
+    if binaryPath == "":
+      raise newException(IOError,
+          "Cannot find Nim standard library: Nim compiler not in PATH")
+    gPrefixDir = binaryPath.splitPath().head.parentDir()
+    #msgs.writelnHook = proc (line: string) = logStr(line)
+
+    loadConfigs(DefaultConfig, cache) # load all config files
+    # now process command line arguments again, because some options in the
+    # command line can overwite the config file's settings
+    options.command = "nimsuggest"
+    let scriptFile = gProjectFull.changeFileExt("nims")
+    if fileExists(scriptFile):
+      runNimScript(cache, scriptFile, freshDefines=false)
+      # 'nim foo.nims' means to just run the NimScript file and do nothing more:
+      if scriptFile == gProjectFull: return
+    elif fileExists(gProjectPath / "config.nims"):
+      # directory wide NimScript file
+      runNimScript(cache, gProjectPath / "config.nims", freshDefines=false)
+
+    extccomp.initVars()
+    processCmdLine(passCmd2, "")
+
+    let graph = newModuleGraph()
+    graph.suggestMode = true
+    mainCommand(graph, cache)
+
+when false:
+  proc quitCalled() {.noconv.} =
+    writeStackTrace()
+
+  addQuitProc(quitCalled)
+
+condsyms.initDefines()
+defineSymbol "nimsuggest"
+handleCmdline(newIdentCache())
diff --git a/tools/nimsuggest/nimsuggest.nim.cfg b/tools/nimsuggest/nimsuggest.nim.cfg
new file mode 100644
index 000000000..949bd18e8
--- /dev/null
+++ b/tools/nimsuggest/nimsuggest.nim.cfg
@@ -0,0 +1,16 @@
+# Special configuration file for the Nim project
+
+gc:markAndSweep
+
+hint[XDeclaredButNotUsed]:off
+
+path:"$lib/packages/docutils"
+
+define:useStdoutAsStdmsg
+define:nimsuggest
+
+#cs:partial
+#define:useNodeIds
+#define:booting
+#define:noDocgen
+--path:"$nim"
diff --git a/tools/nimsuggest/nimsuggest.nimble b/tools/nimsuggest/nimsuggest.nimble
new file mode 100644
index 000000000..3651e12bd
--- /dev/null
+++ b/tools/nimsuggest/nimsuggest.nimble
@@ -0,0 +1,11 @@
+[Package]
+name          = "nimsuggest"
+version       = "0.1.0"
+author        = "Andreas Rumpf"
+description   = "Tool for providing auto completion data for Nim source code."
+license       = "MIT"
+
+bin = "nimsuggest"
+
+[Deps]
+Requires: "nim >= 0.11.2, compiler#head"
diff --git a/tools/nimsuggest/sexp.nim b/tools/nimsuggest/sexp.nim
new file mode 100644
index 000000000..cf08111d7
--- /dev/null
+++ b/tools/nimsuggest/sexp.nim
@@ -0,0 +1,697 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2015 Andreas Rumpf, Dominik Picheta
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+import
+  hashes, strutils, lexbase, streams, unicode, macros
+
+type
+  SexpEventKind* = enum  ## enumeration of all events that may occur when parsing
+    sexpError,           ## an error occurred during parsing
+    sexpEof,             ## end of file reached
+    sexpString,          ## a string literal
+    sexpSymbol,          ## a symbol
+    sexpInt,             ## an integer literal
+    sexpFloat,           ## a float literal
+    sexpNil,             ## the value ``nil``
+    sexpDot,             ## the dot to separate car/cdr
+    sexpListStart,       ## start of a list: the ``(`` token
+    sexpListEnd,         ## end of a list: the ``)`` token
+
+  TTokKind = enum        # must be synchronized with SexpEventKind!
+    tkError,
+    tkEof,
+    tkString,
+    tkSymbol,
+    tkInt,
+    tkFloat,
+    tkNil,
+    tkDot,
+    tkParensLe,
+    tkParensRi
+    tkSpace
+
+  SexpError* = enum        ## enumeration that lists all errors that can occur
+    errNone,               ## no error
+    errInvalidToken,       ## invalid token
+    errParensRiExpected,    ## ``)`` expected
+    errQuoteExpected,      ## ``"`` expected
+    errEofExpected,        ## EOF expected
+
+  SexpParser* = object of BaseLexer ## the parser object.
+    a: string
+    tok: TTokKind
+    kind: SexpEventKind
+    err: SexpError
+
+const
+  errorMessages: array[SexpError, string] = [
+    "no error",
+    "invalid token",
+    "')' expected",
+    "'\"' or \"'\" expected",
+    "EOF expected",
+  ]
+  tokToStr: array[TTokKind, string] = [
+    "invalid token",
+    "EOF",
+    "string literal",
+    "symbol",
+    "int literal",
+    "float literal",
+    "nil",
+    ".",
+    "(", ")", "space"
+  ]
+
+proc close*(my: var SexpParser) {.inline.} =
+  ## closes the parser `my` and its associated input stream.
+  lexbase.close(my)
+
+proc str*(my: SexpParser): string {.inline.} =
+  ## returns the character data for the events: ``sexpInt``, ``sexpFloat``,
+  ## ``sexpString``
+  assert(my.kind in {sexpInt, sexpFloat, sexpString})
+  result = my.a
+
+proc getInt*(my: SexpParser): BiggestInt {.inline.} =
+  ## returns the number for the event: ``sexpInt``
+  assert(my.kind == sexpInt)
+  result = parseBiggestInt(my.a)
+
+proc getFloat*(my: SexpParser): float {.inline.} =
+  ## returns the number for the event: ``sexpFloat``
+  assert(my.kind == sexpFloat)
+  result = parseFloat(my.a)
+
+proc kind*(my: SexpParser): SexpEventKind {.inline.} =
+  ## returns the current event type for the SEXP parser
+  result = my.kind
+
+proc getColumn*(my: SexpParser): int {.inline.} =
+  ## get the current column the parser has arrived at.
+  result = getColNumber(my, my.bufpos)
+
+proc getLine*(my: SexpParser): int {.inline.} =
+  ## get the current line the parser has arrived at.
+  result = my.lineNumber
+
+proc errorMsg*(my: SexpParser): string =
+  ## returns a helpful error message for the event ``sexpError``
+  assert(my.kind == sexpError)
+  result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), errorMessages[my.err]]
+
+proc errorMsgExpected*(my: SexpParser, e: string): string =
+  ## returns an error message "`e` expected" in the same format as the
+  ## other error messages
+  result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), e & " expected"]
+
+proc handleHexChar(c: char, x: var int): bool =
+  result = true # Success
+  case c
+  of '0'..'9': x = (x shl 4) or (ord(c) - ord('0'))
+  of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10)
+  of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10)
+  else: result = false # error
+
+proc parseString(my: var SexpParser): TTokKind =
+  result = tkString
+  var pos = my.bufpos + 1
+  var buf = my.buf
+  while true:
+    case buf[pos]
+    of '\0':
+      my.err = errQuoteExpected
+      result = tkError
+      break
+    of '"':
+      inc(pos)
+      break
+    of '\\':
+      case buf[pos+1]
+      of '\\', '"', '\'', '/':
+        add(my.a, buf[pos+1])
+        inc(pos, 2)
+      of 'b':
+        add(my.a, '\b')
+        inc(pos, 2)
+      of 'f':
+        add(my.a, '\f')
+        inc(pos, 2)
+      of 'n':
+        add(my.a, '\L')
+        inc(pos, 2)
+      of 'r':
+        add(my.a, '\C')
+        inc(pos, 2)
+      of 't':
+        add(my.a, '\t')
+        inc(pos, 2)
+      of 'u':
+        inc(pos, 2)
+        var r: int
+        if handleHexChar(buf[pos], r): inc(pos)
+        if handleHexChar(buf[pos], r): inc(pos)
+        if handleHexChar(buf[pos], r): inc(pos)
+        if handleHexChar(buf[pos], r): inc(pos)
+        add(my.a, toUTF8(Rune(r)))
+      else:
+        # don't bother with the error
+        add(my.a, buf[pos])
+        inc(pos)
+    of '\c':
+      pos = lexbase.handleCR(my, pos)
+      buf = my.buf
+      add(my.a, '\c')
+    of '\L':
+      pos = lexbase.handleLF(my, pos)
+      buf = my.buf
+      add(my.a, '\L')
+    else:
+      add(my.a, buf[pos])
+      inc(pos)
+  my.bufpos = pos # store back
+
+proc parseNumber(my: var SexpParser) =
+  var pos = my.bufpos
+  var buf = my.buf
+  if buf[pos] == '-':
+    add(my.a, '-')
+    inc(pos)
+  if buf[pos] == '.':
+    add(my.a, "0.")
+    inc(pos)
+  else:
+    while buf[pos] in Digits:
+      add(my.a, buf[pos])
+      inc(pos)
+    if buf[pos] == '.':
+      add(my.a, '.')
+      inc(pos)
+  # digits after the dot:
+  while buf[pos] in Digits:
+    add(my.a, buf[pos])
+    inc(pos)
+  if buf[pos] in {'E', 'e'}:
+    add(my.a, buf[pos])
+    inc(pos)
+    if buf[pos] in {'+', '-'}:
+      add(my.a, buf[pos])
+      inc(pos)
+    while buf[pos] in Digits:
+      add(my.a, buf[pos])
+      inc(pos)
+  my.bufpos = pos
+
+proc parseSymbol(my: var SexpParser) =
+  var pos = my.bufpos
+  var buf = my.buf
+  if buf[pos] in IdentStartChars:
+    while buf[pos] in IdentChars:
+      add(my.a, buf[pos])
+      inc(pos)
+  my.bufpos = pos
+
+proc getTok(my: var SexpParser): TTokKind =
+  setLen(my.a, 0)
+  case my.buf[my.bufpos]
+  of '-', '0'..'9': # numbers that start with a . are not parsed
+                    # correctly.
+    parseNumber(my)
+    if {'.', 'e', 'E'} in my.a:
+      result = tkFloat
+    else:
+      result = tkInt
+  of '"': #" # gotta fix nim-mode
+    result = parseString(my)
+  of '(':
+    inc(my.bufpos)
+    result = tkParensLe
+  of ')':
+    inc(my.bufpos)
+    result = tkParensRi
+  of '\0':
+    result = tkEof
+  of 'a'..'z', 'A'..'Z', '_':
+    parseSymbol(my)
+    if my.a == "nil":
+      result = tkNil
+    else:
+      result = tkSymbol
+  of ' ':
+    result = tkSpace
+    inc(my.bufpos)
+  of '.':
+    result = tkDot
+    inc(my.bufpos)
+  else:
+    inc(my.bufpos)
+    result = tkError
+  my.tok = result
+
+# ------------- higher level interface ---------------------------------------
+
+type
+  SexpNodeKind* = enum ## possible SEXP node types
+    SNil,
+    SInt,
+    SFloat,
+    SString,
+    SSymbol,
+    SList,
+    SCons
+
+  SexpNode* = ref SexpNodeObj ## SEXP node
+  SexpNodeObj* {.acyclic.} = object
+    case kind*: SexpNodeKind
+    of SString:
+      str*: string
+    of SSymbol:
+      symbol*: string
+    of SInt:
+      num*: BiggestInt
+    of SFloat:
+      fnum*: float
+    of SList:
+      elems*: seq[SexpNode]
+    of SCons:
+      car: SexpNode
+      cdr: SexpNode
+    of SNil:
+      discard
+
+  Cons = tuple[car: SexpNode, cdr: SexpNode]
+
+  SexpParsingError* = object of ValueError ## is raised for a SEXP error
+
+proc raiseParseErr*(p: SexpParser, msg: string) {.noinline, noreturn.} =
+  ## raises an `ESexpParsingError` exception.
+  raise newException(SexpParsingError, errorMsgExpected(p, msg))
+
+proc newSString*(s: string): SexpNode {.procvar.}=
+  ## Creates a new `SString SexpNode`.
+  new(result)
+  result.kind = SString
+  result.str = s
+
+proc newSStringMove(s: string): SexpNode =
+  new(result)
+  result.kind = SString
+  shallowCopy(result.str, s)
+
+proc newSInt*(n: BiggestInt): SexpNode {.procvar.} =
+  ## Creates a new `SInt SexpNode`.
+  new(result)
+  result.kind = SInt
+  result.num  = n
+
+proc newSFloat*(n: float): SexpNode {.procvar.} =
+  ## Creates a new `SFloat SexpNode`.
+  new(result)
+  result.kind = SFloat
+  result.fnum  = n
+
+proc newSNil*(): SexpNode {.procvar.} =
+  ## Creates a new `SNil SexpNode`.
+  new(result)
+
+proc newSCons*(car, cdr: SexpNode): SexpNode {.procvar.} =
+  ## Creates a new `SCons SexpNode`
+  new(result)
+  result.kind = SCons
+  result.car = car
+  result.cdr = cdr
+
+proc newSList*(): SexpNode {.procvar.} =
+  ## Creates a new `SList SexpNode`
+  new(result)
+  result.kind = SList
+  result.elems = @[]
+
+proc newSSymbol*(s: string): SexpNode {.procvar.} =
+  new(result)
+  result.kind = SSymbol
+  result.symbol = s
+
+proc newSSymbolMove(s: string): SexpNode =
+  new(result)
+  result.kind = SSymbol
+  shallowCopy(result.symbol, s)
+
+proc getStr*(n: SexpNode, default: string = ""): string =
+  ## Retrieves the string value of a `SString SexpNode`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``SString``.
+  if n.kind != SString: return default
+  else: return n.str
+
+proc getNum*(n: SexpNode, default: BiggestInt = 0): BiggestInt =
+  ## Retrieves the int value of a `SInt SexpNode`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``SInt``.
+  if n.kind != SInt: return default
+  else: return n.num
+
+proc getFNum*(n: SexpNode, default: float = 0.0): float =
+  ## Retrieves the float value of a `SFloat SexpNode`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``SFloat``.
+  if n.kind != SFloat: return default
+  else: return n.fnum
+
+proc getSymbol*(n: SexpNode, default: string = ""): string =
+  ## Retrieves the int value of a `SList SexpNode`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``SList``.
+  if n.kind != SSymbol: return default
+  else: return n.symbol
+
+proc getElems*(n: SexpNode, default: seq[SexpNode] = @[]): seq[SexpNode] =
+  ## Retrieves the int value of a `SList SexpNode`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``SList``.
+  if n.kind == SNil: return @[]
+  elif n.kind != SList: return default
+  else: return n.elems
+
+proc getCons*(n: SexpNode, defaults: Cons = (newSNil(), newSNil())): Cons =
+  ## Retrieves the cons value of a `SList SexpNode`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``SList``.
+  if n.kind == SCons: return (n.car, n.cdr)
+  elif n.kind == SList: return (n.elems[0], n.elems[1])
+  else: return defaults
+
+proc sexp*(s: string): SexpNode =
+  ## Generic constructor for SEXP data. Creates a new `SString SexpNode`.
+  new(result)
+  result.kind = SString
+  result.str = s
+
+proc sexp*(n: BiggestInt): SexpNode =
+  ## Generic constructor for SEXP data. Creates a new `SInt SexpNode`.
+  new(result)
+  result.kind = SInt
+  result.num  = n
+
+proc sexp*(n: float): SexpNode =
+  ## Generic constructor for SEXP data. Creates a new `SFloat SexpNode`.
+  new(result)
+  result.kind = SFloat
+  result.fnum  = n
+
+proc sexp*(b: bool): SexpNode =
+  ## Generic constructor for SEXP data. Creates a new `SSymbol
+  ## SexpNode` with value t or `SNil SexpNode`.
+  new(result)
+  if b:
+    result.kind = SSymbol
+    result.symbol = "t"
+  else:
+    result.kind = SNil
+
+proc sexp*(elements: openArray[SexpNode]): SexpNode =
+  ## Generic constructor for SEXP data. Creates a new `SList SexpNode`
+  new(result)
+  result.kind = SList
+  newSeq(result.elems, elements.len)
+  for i, p in pairs(elements): result.elems[i] = p
+
+proc sexp*(s: SexpNode): SexpNode =
+  result = s
+
+proc toSexp(x: NimNode): NimNode {.compiletime.} =
+  case x.kind
+  of nnkBracket:
+    result = newNimNode(nnkBracket)
+    for i in 0 .. <x.len:
+      result.add(toSexp(x[i]))
+
+  else:
+    result = x
+
+  result = prefix(result, "sexp")
+
+macro convertSexp*(x: untyped): untyped =
+  ## Convert an expression to a SexpNode directly, without having to specify
+  ## `%` for every element.
+  result = toSexp(x)
+
+proc `==`* (a,b: SexpNode): bool =
+  ## Check two nodes for equality
+  if a.isNil:
+    if b.isNil: return true
+    return false
+  elif b.isNil or a.kind != b.kind:
+    return false
+  else:
+    return case a.kind
+    of SString:
+      a.str == b.str
+    of SInt:
+      a.num == b.num
+    of SFloat:
+      a.fnum == b.fnum
+    of SNil:
+      true
+    of SList:
+      a.elems == b.elems
+    of SSymbol:
+      a.symbol == b.symbol
+    of SCons:
+      a.car == b.car and a.cdr == b.cdr
+
+proc hash* (n:SexpNode): Hash =
+  ## Compute the hash for a SEXP node
+  case n.kind
+  of SList:
+    result = hash(n.elems)
+  of SInt:
+    result = hash(n.num)
+  of SFloat:
+    result = hash(n.fnum)
+  of SString:
+    result = hash(n.str)
+  of SNil:
+    result = hash(0)
+  of SSymbol:
+    result = hash(n.symbol)
+  of SCons:
+    result = hash(n.car) !& hash(n.cdr)
+
+proc len*(n: SexpNode): int =
+  ## If `n` is a `SList`, it returns the number of elements.
+  ## If `n` is a `JObject`, it returns the number of pairs.
+  ## Else it returns 0.
+  case n.kind
+  of SList: result = n.elems.len
+  else: discard
+
+proc `[]`*(node: SexpNode, index: int): SexpNode =
+  ## Gets the node at `index` in a List. Result is undefined if `index`
+  ## is out of bounds
+  assert(not isNil(node))
+  assert(node.kind == SList)
+  return node.elems[index]
+
+proc add*(father, child: SexpNode) =
+  ## Adds `child` to a SList node `father`.
+  assert father.kind == SList
+  father.elems.add(child)
+
+# ------------- pretty printing ----------------------------------------------
+
+proc indent(s: var string, i: int) =
+  s.add(spaces(i))
+
+proc newIndent(curr, indent: int, ml: bool): int =
+  if ml: return curr + indent
+  else: return indent
+
+proc nl(s: var string, ml: bool) =
+  if ml: s.add("\n")
+
+proc escapeJson*(s: string): string =
+  ## Converts a string `s` to its JSON representation.
+  result = newStringOfCap(s.len + s.len shr 3)
+  result.add("\"")
+  for x in runes(s):
+    var r = int(x)
+    if r >= 32 and r <= 127:
+      var c = chr(r)
+      case c
+      of '"': result.add("\\\"") #" # gotta fix nim-mode
+      of '\\': result.add("\\\\")
+      else: result.add(c)
+    else:
+      result.add("\\u")
+      result.add(toHex(r, 4))
+  result.add("\"")
+
+proc copy*(p: SexpNode): SexpNode =
+  ## Performs a deep copy of `a`.
+  case p.kind
+  of SString:
+    result = newSString(p.str)
+  of SInt:
+    result = newSInt(p.num)
+  of SFloat:
+    result = newSFloat(p.fnum)
+  of SNil:
+    result = newSNil()
+  of SSymbol:
+    result = newSSymbol(p.symbol)
+  of SList:
+    result = newSList()
+    for i in items(p.elems):
+      result.elems.add(copy(i))
+  of SCons:
+    result = newSCons(copy(p.car), copy(p.cdr))
+
+proc toPretty(result: var string, node: SexpNode, indent = 2, ml = true,
+              lstArr = false, currIndent = 0) =
+  case node.kind
+  of SString:
+    if lstArr: result.indent(currIndent)
+    result.add(escapeJson(node.str))
+  of SInt:
+    if lstArr: result.indent(currIndent)
+    result.add($node.num)
+  of SFloat:
+    if lstArr: result.indent(currIndent)
+    result.add($node.fnum)
+  of SNil:
+    if lstArr: result.indent(currIndent)
+    result.add("nil")
+  of SSymbol:
+    if lstArr: result.indent(currIndent)
+    result.add($node.symbol)
+  of SList:
+    if lstArr: result.indent(currIndent)
+    if len(node.elems) != 0:
+      result.add("(")
+      result.nl(ml)
+      for i in 0..len(node.elems)-1:
+        if i > 0:
+          result.add(" ")
+          result.nl(ml) # New Line
+        toPretty(result, node.elems[i], indent, ml,
+            true, newIndent(currIndent, indent, ml))
+      result.nl(ml)
+      result.indent(currIndent)
+      result.add(")")
+    else: result.add("nil")
+  of SCons:
+    if lstArr: result.indent(currIndent)
+    result.add("(")
+    toPretty(result, node.car, indent, ml,
+        true, newIndent(currIndent, indent, ml))
+    result.add(" . ")
+    toPretty(result, node.cdr, indent, ml,
+        true, newIndent(currIndent, indent, ml))
+    result.add(")")
+
+proc pretty*(node: SexpNode, indent = 2): string =
+  ## Converts `node` to its Sexp Representation, with indentation and
+  ## on multiple lines.
+  result = ""
+  toPretty(result, node, indent)
+
+proc `$`*(node: SexpNode): string =
+  ## Converts `node` to its SEXP Representation on one line.
+  result = ""
+  toPretty(result, node, 0, false)
+
+iterator items*(node: SexpNode): SexpNode =
+  ## Iterator for the items of `node`. `node` has to be a SList.
+  assert node.kind == SList
+  for i in items(node.elems):
+    yield i
+
+iterator mitems*(node: var SexpNode): var SexpNode =
+  ## Iterator for the items of `node`. `node` has to be a SList. Items can be
+  ## modified.
+  assert node.kind == SList
+  for i in mitems(node.elems):
+    yield i
+
+proc eat(p: var SexpParser, tok: TTokKind) =
+  if p.tok == tok: discard getTok(p)
+  else: raiseParseErr(p, tokToStr[tok])
+
+proc parseSexp(p: var SexpParser): SexpNode =
+  ## Parses SEXP from a SEXP Parser `p`.
+  case p.tok
+  of tkString:
+    # we capture 'p.a' here, so we need to give it a fresh buffer afterwards:
+    result = newSStringMove(p.a)
+    p.a = ""
+    discard getTok(p)
+  of tkInt:
+    result = newSInt(parseBiggestInt(p.a))
+    discard getTok(p)
+  of tkFloat:
+    result = newSFloat(parseFloat(p.a))
+    discard getTok(p)
+  of tkNil:
+    result = newSNil()
+    discard getTok(p)
+  of tkSymbol:
+    result = newSSymbolMove(p.a)
+    p.a = ""
+    discard getTok(p)
+  of tkParensLe:
+    result = newSList()
+    discard getTok(p)
+    while p.tok notin {tkParensRi, tkDot}:
+      result.add(parseSexp(p))
+      if p.tok != tkSpace: break
+      discard getTok(p)
+    if p.tok == tkDot:
+      eat(p, tkDot)
+      eat(p, tkSpace)
+      result.add(parseSexp(p))
+      result = newSCons(result[0], result[1])
+    eat(p, tkParensRi)
+  of tkSpace, tkDot, tkError, tkParensRi, tkEof:
+    raiseParseErr(p, "(")
+
+proc open*(my: var SexpParser, input: Stream) =
+  ## initializes the parser with an input stream.
+  lexbase.open(my, input)
+  my.kind = sexpError
+  my.a = ""
+
+proc parseSexp*(s: Stream): SexpNode =
+  ## Parses from a buffer `s` into a `SexpNode`.
+  var p: SexpParser
+  p.open(s)
+  discard getTok(p) # read first token
+  result = p.parseSexp()
+  p.close()
+
+proc parseSexp*(buffer: string): SexpNode =
+  ## Parses Sexp from `buffer`.
+  result = parseSexp(newStringStream(buffer))
+
+when isMainModule:
+  let testSexp = parseSexp("""(1 (98 2) nil (2) foobar "foo" 9.234)""")
+  assert(testSexp[0].getNum == 1)
+  assert(testSexp[1][0].getNum == 98)
+  assert(testSexp[2].getElems == @[])
+  assert(testSexp[4].getSymbol == "foobar")
+  assert(testSexp[5].getStr == "foo")
+
+  let alist = parseSexp("""((1 . 2) (2 . "foo"))""")
+  assert(alist[0].getCons.car.getNum == 1)
+  assert(alist[0].getCons.cdr.getNum == 2)
+  assert(alist[1].getCons.cdr.getStr == "foo")
+
+  # Generator:
+  var j = convertSexp([true, false, "foobar", [1, 2, "baz"]])
+  assert($j == """(t nil "foobar" (1 2 "baz"))""")
diff --git a/tools/nimsuggest/tester.nim b/tools/nimsuggest/tester.nim
new file mode 100644
index 000000000..c90afe3db
--- /dev/null
+++ b/tools/nimsuggest/tester.nim
@@ -0,0 +1,182 @@
+# Tester for nimsuggest.
+# Every test file can have a #[!]# comment that is deleted from the input
+# before 'nimsuggest' is invoked to ensure this token doesn't make a
+# crucial difference for Nim's parser.
+
+import os, osproc, strutils, streams, re
+
+type
+  Test = object
+    cmd, dest: string
+    startup: seq[string]
+    script: seq[(string, string)]
+
+const
+  curDir = when defined(windows): "" else: ""
+  DummyEof = "!EOF!"
+
+template tpath(): untyped = getAppDir() / "tests"
+
+proc parseTest(filename: string): Test =
+  const cursorMarker = "#[!]#"
+  let nimsug = curDir & addFileExt("nimsuggest", ExeExt)
+  result.dest = getTempDir() / extractFilename(filename)
+  result.cmd = nimsug & " --tester " & result.dest
+  result.script = @[]
+  result.startup = @[]
+  var tmp = open(result.dest, fmWrite)
+  var specSection = 0
+  var markers = newSeq[string]()
+  var i = 1
+  for x in lines(filename):
+    let marker = x.find(cursorMarker)+1
+    if marker > 0:
+      markers.add "\"" & filename & "\";\"" & result.dest & "\":" & $i & ":" & $marker
+      tmp.writeLine x.replace(cursorMarker, "")
+    else:
+      tmp.writeLine x
+    if x.contains("""""""""):
+      inc specSection
+    elif specSection == 1:
+      if x.startsWith("$nimsuggest"):
+        result.cmd = x % ["nimsuggest", nimsug, "file", filename]
+      elif x.startsWith("!"):
+        if result.cmd.len == 0:
+          result.startup.add x
+        else:
+          result.script.add((x, ""))
+      elif x.startsWith(">"):
+        # since 'markers' here are not complete yet, we do the $substitutions
+        # afterwards
+        result.script.add((x.substr(1).replaceWord("$path", tpath()), ""))
+      elif x.len > 0:
+        # expected output line:
+        let x = x % ["file", filename]
+        result.script[^1][1].add x.replace(";;", "\t") & '\L'
+        # else: ignore empty lines for better readability of the specs
+    inc i
+  tmp.close()
+  # now that we know the markers, substitute them:
+  for a in mitems(result.script):
+    a[0] = a[0] % markers
+
+proc parseCmd(c: string): seq[string] =
+  # we don't support double quotes for now so that
+  # we can later support them properly with escapes and stuff.
+  result = @[]
+  var i = 0
+  var a = ""
+  while true:
+    setLen(a, 0)
+    # eat all delimiting whitespace
+    while c[i] in {' ', '\t', '\l', '\r'}: inc(i)
+    case c[i]
+    of '"': raise newException(ValueError, "double quotes not yet supported: " & c)
+    of '\'':
+      var delim = c[i]
+      inc(i) # skip ' or "
+      while c[i] != '\0' and c[i] != delim:
+        add a, c[i]
+        inc(i)
+      if c[i] != '\0': inc(i)
+    of '\0': break
+    else:
+      while c[i] > ' ':
+        add(a, c[i])
+        inc(i)
+    add(result, a)
+
+proc edit(tmpfile: string; x: seq[string]) =
+  if x.len != 3 and x.len != 4:
+    quit "!edit takes two or three arguments"
+  let f = if x.len >= 4: tpath() / x[3] else: tmpfile
+  try:
+    let content = readFile(f)
+    let newcontent = content.replace(x[1], x[2])
+    if content == newcontent:
+      quit "wrong test case: edit had no effect"
+    writeFile(f, newcontent)
+  except IOError:
+    quit "cannot edit file " & tmpfile
+
+proc exec(x: seq[string]) =
+  if x.len != 2: quit "!exec takes one argument"
+  if execShellCmd(x[1]) != 0:
+    quit "External program failed " & x[1]
+
+proc copy(x: seq[string]) =
+  if x.len != 3: quit "!copy takes two arguments"
+  let rel = tpath()
+  copyFile(rel / x[1], rel / x[2])
+
+proc del(x: seq[string]) =
+  if x.len != 2: quit "!del takes one argument"
+  removeFile(tpath() / x[1])
+
+proc runCmd(cmd, dest: string): bool =
+  result = cmd[0] == '!'
+  if not result: return
+  let x = cmd.parseCmd()
+  case x[0]
+  of "!edit":
+    edit(dest, x)
+  of "!exec":
+    exec(x)
+  of "!copy":
+    copy(x)
+  of "!del":
+    del(x)
+  else:
+    quit "unkown command: " & cmd
+
+proc smartCompare(pattern, x: string): bool =
+  if pattern.contains('*'):
+    result = match(x, re(escapeRe(pattern).replace("\\x2A","(.*)"), {}))
+
+proc runTest(filename: string): int =
+  let s = parseTest filename
+  for cmd in s.startup:
+    if not runCmd(cmd, s.dest):
+      quit "invalid command: " & cmd
+  let cl = parseCmdLine(s.cmd)
+  var p = startProcess(command=cl[0], args=cl[1 .. ^1],
+                       options={poStdErrToStdOut, poUsePath,
+                       poInteractive, poDemon})
+  let outp = p.outputStream
+  let inp = p.inputStream
+  var report = ""
+  var a = newStringOfCap(120)
+  try:
+    # read and ignore anything nimsuggest says at startup:
+    while outp.readLine(a):
+      if a == DummyEof: break
+    for req, resp in items(s.script):
+      if not runCmd(req, s.dest):
+        inp.writeLine(req)
+        inp.flush()
+        var answer = ""
+        while outp.readLine(a):
+          if a == DummyEof: break
+          answer.add a
+          answer.add '\L'
+        if resp != answer and not smartCompare(resp, answer):
+          report.add "\nTest failed: " & filename
+          report.add "\n  Expected:  " & resp
+          report.add "\n  But got:   " & answer
+  finally:
+    inp.writeLine("quit")
+    inp.flush()
+    close(p)
+  if report.len > 0:
+    echo report
+  result = report.len
+
+proc main() =
+  var failures = 0
+  for x in walkFiles(getAppDir() / "tests/t*.nim"):
+    echo "Test ", x
+    failures += runTest(expandFilename(x))
+  if failures > 0:
+    quit 1
+
+main()
diff --git a/tools/nimsuggest/tests/dep_v1.nim b/tools/nimsuggest/tests/dep_v1.nim
new file mode 100644
index 000000000..eae230e85
--- /dev/null
+++ b/tools/nimsuggest/tests/dep_v1.nim
@@ -0,0 +1,8 @@
+
+
+
+
+
+type
+  Foo* = object
+    x*, y*: int
diff --git a/tools/nimsuggest/tests/dep_v2.nim b/tools/nimsuggest/tests/dep_v2.nim
new file mode 100644
index 000000000..ab39721c4
--- /dev/null
+++ b/tools/nimsuggest/tests/dep_v2.nim
@@ -0,0 +1,9 @@
+
+
+
+
+
+type
+  Foo* = object
+    x*, y*: int
+    z*: string
diff --git a/tools/nimsuggest/tests/tdef1.nim b/tools/nimsuggest/tests/tdef1.nim
new file mode 100644
index 000000000..960ffad1c
--- /dev/null
+++ b/tools/nimsuggest/tests/tdef1.nim
@@ -0,0 +1,16 @@
+discard """
+$nimsuggest --tester $file
+>def $1
+def;;skProc;;tdef1.hello;;proc ();;$file;;9;;5;;"";;100
+>def $1
+def;;skProc;;tdef1.hello;;proc ();;$file;;9;;5;;"";;100
+"""
+
+proc hello() string =
+  ## Return hello
+  "Hello"
+
+hel#[!]#lo()
+
+# v uncompleted id for sug (13,2)
+he
diff --git a/tools/nimsuggest/tests/tdot1.nim b/tools/nimsuggest/tests/tdot1.nim
new file mode 100644
index 000000000..bcd44cd84
--- /dev/null
+++ b/tools/nimsuggest/tests/tdot1.nim
@@ -0,0 +1,14 @@
+discard """
+$nimsuggest --tester $file
+>sug $1
+sug;;skField;;x;;int;;$file;;11;;4;;"";;100
+sug;;skField;;y;;int;;$file;;11;;7;;"";;100
+sug;;skProc;;tdot1.main;;proc (f: Foo);;$file;;13;;5;;"";;100
+"""
+
+type
+  Foo = object
+    x, y: int
+
+proc main(f: Foo) =
+  f.#[!]#
diff --git a/tools/nimsuggest/tests/tdot2.nim b/tools/nimsuggest/tests/tdot2.nim
new file mode 100644
index 000000000..a58ac818b
--- /dev/null
+++ b/tools/nimsuggest/tests/tdot2.nim
@@ -0,0 +1,29 @@
+# Test basic editing. We replace the 'false' by 'true' to
+# see whether then the z field is suggested.
+
+const zField = 0i32
+
+type
+  Foo = object
+    x, y: int
+    when zField == 1i32:
+      z: string
+
+proc main(f: Foo) =
+  f.#[!]#
+
+# the tester supports the spec section at the bottom of the file and
+# this way, the line numbers more often stay the same
+discard """
+$nimsuggest --tester $file
+>sug $1
+sug;;skField;;x;;int;;$file;;8;;4;;"";;100
+sug;;skField;;y;;int;;$file;;8;;7;;"";;100
+sug;;skProc;;tdot2.main;;proc (f: Foo);;$file;;12;;5;;"";;100
+!edit 0i32 1i32
+>sug $1
+sug;;skField;;x;;int;;$file;;8;;4;;"";;100
+sug;;skField;;y;;int;;$file;;8;;7;;"";;100
+sug;;skField;;z;;string;;$file;;10;;6;;"";;100
+sug;;skProc;;tdot2.main;;proc (f: Foo);;$file;;12;;5;;"";;100
+"""
diff --git a/tools/nimsuggest/tests/tdot3.nim b/tools/nimsuggest/tests/tdot3.nim
new file mode 100644
index 000000000..5badde867
--- /dev/null
+++ b/tools/nimsuggest/tests/tdot3.nim
@@ -0,0 +1,27 @@
+# Test basic module dependency recompilations.
+
+import dep
+
+proc main(f: Foo) =
+  f.#[!]#
+
+# the tester supports the spec section at the bottom of the file and
+# this way, the line numbers more often stay the same
+
+discard """
+!copy dep_v1.nim dep.nim
+$nimsuggest --tester $file
+>sug $1
+sug;;skField;;x;;int;;*dep.nim;;8;;4;;"";;100
+sug;;skField;;y;;int;;*dep.nim;;8;;8;;"";;100
+sug;;skProc;;tdot3.main;;proc (f: Foo);;$file;;5;;5;;"";;100
+
+!copy dep_v2.nim dep.nim
+>mod $path/dep.nim
+>sug $1
+sug;;skField;;x;;int;;*dep.nim;;8;;4;;"";;100
+sug;;skField;;y;;int;;*dep.nim;;8;;8;;"";;100
+sug;;skField;;z;;string;;*dep.nim;;9;;4;;"";;100
+sug;;skProc;;tdot3.main;;proc (f: Foo);;$file;;5;;5;;"";;100
+!del dep.nim
+"""
diff --git a/tools/nimsuggest/tests/tinclude.nim b/tools/nimsuggest/tests/tinclude.nim
new file mode 100644
index 000000000..77492d745
--- /dev/null
+++ b/tools/nimsuggest/tests/tinclude.nim
@@ -0,0 +1,7 @@
+discard """
+$nimsuggest --tester compiler/nim.nim
+>def compiler/semexprs.nim:13:50
+def;;skType;;ast.PSym;;PSym;;*ast.nim;;668;;2;;"";;100
+>def compiler/semexprs.nim:13:50
+def;;skType;;ast.PSym;;PSym;;*ast.nim;;668;;2;;"";;100
+"""
diff --git a/tools/nimsuggest/tests/tstrutils.nim b/tools/nimsuggest/tests/tstrutils.nim
new file mode 100644
index 000000000..f5cda9505
--- /dev/null
+++ b/tools/nimsuggest/tests/tstrutils.nim
@@ -0,0 +1,9 @@
+discard """
+$nimsuggest --tester lib/pure/strutils.nim
+>def lib/pure/strutils.nim:2300:6
+def;;skTemplate;;system.doAssert;;proc (cond: bool, msg: string): typed;;*/lib/system.nim;;*;;9;;"";;100
+"""
+
+# Line 2300 in strutils.nim is doAssert and this is unlikely to change
+# soon since there are a whole lot of doAsserts there.
+
diff --git a/tools/nimweb.nim b/tools/nimweb.nim
index 65af67216..9a21a0fb5 100644
--- a/tools/nimweb.nim
+++ b/tools/nimweb.nim
@@ -29,7 +29,7 @@ type
   TRssItem = object
     year, month, day, title, url, content: string
   TAction = enum
-    actAll, actOnlyWebsite, actPdf, actJson2
+    actAll, actOnlyWebsite, actPdf, actJson2, actOnlyDocs
 
   Sponsor = object
     logo: string
@@ -72,7 +72,7 @@ include "website.tmpl"
 # ------------------------- configuration file -------------------------------
 
 const
-  version = "0.7"
+  version = "0.8"
   usage = "nimweb - Nim Website Generator Version " & version & """
 
   (c) 2015 Andreas Rumpf
@@ -85,11 +85,13 @@ Options:
   -v, --version       shows the version
   --website           only build the website, not the full documentation
   --pdf               build the PDF version of the documentation
+  --json2             build JSON of the documentation
+  --onlyDocs          build only the documentation
 Compile_options:
   will be passed to the Nim compiler
 """
 
-  rYearMonthDay = r"(\d{4})_(\d{2})_(\d{2})"
+  rYearMonthDay = r"on\s+(\d{2})\/(\d{2})\/(\d{4})"
   rssUrl = "http://nim-lang.org/news.xml"
   rssNewsUrl = "http://nim-lang.org/news.html"
   activeSponsors = "web/sponsors.csv"
@@ -157,6 +159,7 @@ proc parseCmdLine(c: var TConfigData) =
       of "website": action = actOnlyWebsite
       of "pdf": action = actPdf
       of "json2": action = actJson2
+      of "onlydocs": action = actOnlyDocs
       of "googleanalytics":
         c.gaId = val
         c.nimArgs.add("--doc.googleAnalytics:" & val & " ")
@@ -315,6 +318,7 @@ proc buildDoc(c: var TConfigData, destPath: string) =
   exec(findNim() & " buildIndex -o:$1/theindex.html $1" % [destPath])
 
 proc buildPdfDoc(c: var TConfigData, destPath: string) =
+  createDir(destPath)
   if os.execShellCmd("pdflatex -version") != 0:
     echo "pdflatex not found; no PDF documentation generated"
   else:
@@ -347,6 +351,7 @@ proc buildAddDoc(c: var TConfigData, destPath: string) =
 proc parseNewsTitles(inputFilename: string): seq[TRssItem] =
   # Goes through each news file, returns its date/title.
   result = @[]
+  var matches: array[3, string]
   let reYearMonthDay = re(rYearMonthDay)
   for kind, path in walkDir(inputFilename):
     let (dir, name, ext) = path.splitFile
@@ -354,8 +359,8 @@ proc parseNewsTitles(inputFilename: string): seq[TRssItem] =
       let content = readFile(path)
       let title = content.splitLines()[0]
       let urlPath = "news/" & name & ".html"
-      if name =~ reYearMonthDay:
-        result.add(TRssItem(year: matches[0], month: matches[1], day: matches[2],
+      if content.find(reYearMonthDay, matches) >= 0:
+        result.add(TRssItem(year: matches[2], month: matches[1], day: matches[0],
           title: title, url: "http://nim-lang.org/" & urlPath,
           content: content))
   result.reverse()
@@ -501,11 +506,19 @@ proc buildWebsite(c: var TConfigData) =
 proc main(c: var TConfigData) =
   buildWebsite(c)
   buildJS("web/upload")
-  buildAddDoc(c, "web/upload")
-  buildDocSamples(c, "web/upload")
-  buildDoc(c, "web/upload")
-  buildDocSamples(c, "doc")
-  buildDoc(c, "doc")
+  const docup = "web/upload/" & NimVersion
+  createDir(docup)
+  buildAddDoc(c, docup)
+  buildDocSamples(c, docup)
+  buildDoc(c, docup)
+  createDir("doc/html")
+  buildDocSamples(c, "doc/html")
+  buildDoc(c, "doc/html")
+
+proc onlyDocs(c: var TConfigData) =
+  createDir("doc/html")
+  buildDocSamples(c, "doc/html")
+  buildDoc(c, "doc/html")
 
 proc json2(c: var TConfigData) =
   const destPath = "web/json2"
@@ -526,6 +539,7 @@ parseCmdLine(c)
 parseIniFile(c)
 case action
 of actOnlyWebsite: buildWebsite(c)
-of actPdf: buildPdfDoc(c, "doc")
+of actPdf: buildPdfDoc(c, "doc/pdf")
+of actOnlyDocs: onlyDocs(c)
 of actAll: main(c)
 of actJson2: json2(c)
diff --git a/tools/noprefix.nim b/tools/noprefix.nim
deleted file mode 100644
index d16c2b520..000000000
--- a/tools/noprefix.nim
+++ /dev/null
@@ -1,32 +0,0 @@
-# strip those silly GTK/ATK prefixes...
-
-import
-  expandimportc, os
-
-const
-  filelist = [
-    ("sdl/sdl", "sdl"),
-    ("sdl/sdl_net", "sdl"),
-    ("sdl/sdl_gfx", "sdl"),
-    ("sdl/sdl_image", "sdl"),
-    ("sdl/sdl_mixer_nosmpeg", "sdl"),
-    ("sdl/sdl_mixer", "sdl"),
-    ("sdl/sdl_ttf", "sdl"),
-    ("sdl/smpeg", "sdl"),
-
-    ("libcurl", "curl"),
-    ("mysql", "mysql"),
-    ("postgres", ""),
-    ("sqlite3", "sqlite3"),
-
-    ("pcre/pcre", "pcre")
-  ]
-
-proc createDirs =
-  createDir("lib/newwrap/sdl")
-  createDir("lib/newwrap/pcre")
-
-for filename, prefix in items(filelist):
-  var f = addFileExt(filename, "nim")
-  main("lib/wrappers" / f, "lib/newwrap" / f, prefix)
-
diff --git a/tools/start.bat b/tools/start.bat
new file mode 100644
index 000000000..a4475fac7
--- /dev/null
+++ b/tools/start.bat
@@ -0,0 +1,8 @@
+@echo off

+REM COLOR 0A

+SET NIMPATH=%~dp0\..

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

+cd %NIMPATH%

+cmd 

+

+

diff --git a/tools/vccenv/vccenv.nim b/tools/vccenv/vccenv.nim
new file mode 100644
index 000000000..a335efd10
--- /dev/null
+++ b/tools/vccenv/vccenv.nim
@@ -0,0 +1,58 @@
+import strtabs, os, osproc, streams, strutils
+
+const
+  comSpecEnvKey = "ComSpec"
+  vsComnToolsEnvKeys = [
+    "VS140COMNTOOLS",
+    "VS130COMNTOOLS",
+    "VS120COMNTOOLS",
+    "VS110COMNTOOLS",
+    "VS100COMNTOOLS",
+    "VS90COMNTOOLS"
+  ]
+  vcvarsallRelativePath = joinPath("..", "..", "VC", "vcvarsall")
+
+proc getVsComnToolsPath*(): TaintedString =
+  for vsComnToolsEnvKey in vsComnToolsEnvKeys:
+    let vsComnToolsEnvVal = getEnv vsComnToolsEnvKey
+    if vsComnToolsEnvVal.len > 0:
+      return vsComnToolsEnvVal
+
+proc getVccEnv*(platform: string, windowsStoreSdk: bool = false,
+                sdkVersion: string = nil): StringTableRef =
+  var comSpecCommandString = getEnv comSpecEnvKey
+  if comSpecCommandString.len == 0:
+    comSpecCommandString = "cmd"
+
+  let vsComnToolsPath = getVsComnToolsPath()
+  if vsComnToolsPath.len < 1:
+    return nil
+  let vcvarsallPath = expandFilename joinPath(vsComnToolsPath, vcvarsallRelativePath)
+
+  var vcvarsallArgs: seq[string] = @[]
+  if platform.len > 0:
+    vcvarsallArgs.add(platform)
+  if windowsStoreSdk:
+    vcvarsallArgs.add("store")
+  if sdkVersion.len > 0:
+    vcvarsallArgs.add(sdkVersion)
+  let vcvarsallArgString = vcvarsallArgs.join(" ")
+
+  var vcvarsallCommandString: string
+  if vcvarsallArgString.len > 0:
+    vcvarsallCommandString = "\"$1\" $2" % [vcvarsallPath, vcvarsallArgString]
+  else:
+    vcvarsallCommandString = vcvarsallPath
+
+  let vcvarsallExecCommand = "\"$1\" /C \"$2 && SET\"" %
+                             [comSpecCommandString, vcvarsallCommandString]
+  when defined(release):
+    let vccvarsallOptions = {poEvalCommand, poDemon}
+  else:
+    let vccvarsallOptions = {poEchoCmd, poEvalCommand, poDemon}
+  let vcvarsallStdOut = execProcess(vcvarsallExecCommand, options = vccvarsallOptions)
+  result = newStringTable(modeCaseInsensitive)
+  for line in vcvarsallStdOut.splitLines:
+    let idx = line.find('=')
+    if idx > 0:
+      result[line[0..(idx - 1)]] = line[(idx + 1)..(line.len - 1)]
diff --git a/tools/vccenv/vccexe.nim b/tools/vccenv/vccexe.nim
new file mode 100644
index 000000000..892246830
--- /dev/null
+++ b/tools/vccenv/vccexe.nim
@@ -0,0 +1,66 @@
+import strutils, strtabs, os, osproc, vccenv
+
+when defined(release):
+  let vccOptions = {poParentStreams}
+else:
+  let vccOptions = {poEchoCmd, poParentStreams}
+
+const 
+  platformPrefix = "--platform"
+  winstorePrefix = "--winstore"
+  sdkversionPrefix = "--sdkversion"
+
+  platformSepIdx = platformPrefix.len
+  sdkversionSepIdx = sdkversionPrefix.len
+  
+  HelpText = """
++-----------------------------------------------------------------+
+|         Microsoft C/C++ compiler wrapper for Nim                |
+|             (c) 2016 Fredrik Høisæther Rasch                    |
++-----------------------------------------------------------------+
+
+Usage:
+  vccexe [options] [compileroptions]
+Options:
+  --platform:<arch>   Specify the Compiler Platform Tools architecture
+                      <arch>: x86 | amd64 | arm | x86_amd64 | x86_arm | amd64_x86 | amd64_arm
+  --winstore          Use Windows Store (rather than desktop) development tools
+  --sdkversion:<v>    Use a specific Windows SDK version:
+                      <v> is either the full Windows 10 SDK version number or 
+                      "8.1" to use the windows 8.1 SDK
+
+Other command line arguments are passed on to the
+Microsoft C/C++ compiler for the specified SDK toolset
+"""
+
+when isMainModule:
+  var platformArg: string = nil
+  var sdkVersionArg: string = nil
+  var storeArg: bool = false
+
+  var clArgs: seq[TaintedString] = @[]
+
+  var wrapperArgs = commandLineParams()
+  for wargv in wrapperArgs:
+    # Check whether the current argument contains -- prefix
+    if wargv.startsWith(platformPrefix): # Check for platform
+      platformArg = wargv.substr(platformSepIdx + 1)
+    elif wargv == winstorePrefix: # Check for winstore
+      storeArg = true
+    elif wargv.startsWith(sdkversionPrefix): # Check for sdkversion
+      sdkVersionArg = wargv.substr(sdkversionSepIdx + 1)
+    else: # Regular cl.exe argument -> store for final cl.exe invocation
+      if (wargv.len == 2) and (wargv[1] == '?'):
+        echo HelpText
+      clArgs.add(wargv)
+
+  var vccEnvStrTab = getVccEnv(platformArg, storeArg, sdkVersionArg)  
+  if vccEnvStrTab != nil:
+    for vccEnvKey, vccEnvVal in vccEnvStrTab:
+      putEnv(vccEnvKey, vccEnvVal)
+  let vccProcess = startProcess(
+      "cl.exe",
+      args = clArgs,
+      options = vccOptions
+    )
+  quit vccProcess.waitForExit()
diff --git a/tools/website.tmpl b/tools/website.tmpl
index 2801fea96..9aa64310d 100644
--- a/tools/website.tmpl
+++ b/tools/website.tmpl
@@ -61,13 +61,13 @@
         <div id="slideshow">
           <!-- slides -->
           <div id="slide0" class="active niaslide">
-            <a href="sponsors.html">
-              <img src="${rootDir}assets/bountysource/meet_sponsors.png" alt="Meet our BountySource sponsors!"/>
+            <a href="news/e030_nim_in_action_in_production.html">
+              <img src="${rootDir}assets/niminaction/banner2.png" alt="A printed copy of Nim in Action should be available in March 2017!"/>
             </a>
           </div>
           <div id="slide1" class="niaslide">
-            <a href="news/2016_01_27_nim_in_action_is_now_available.html">
-              <img src="${rootDir}assets/niminaction/banner.jpg" alt="New in Manning Early Access Program: Nim in Action!"/>
+            <a href="sponsors.html">
+              <img src="${rootDir}assets/bountysource/meet_sponsors.png" alt="Meet our BountySource sponsors!"/>
             </a>
           </div>
           <div id="slide2" class="codeslide2">