diff options
Diffstat (limited to 'tools')
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"> |