diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2018-09-07 01:53:09 +0200 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2018-09-07 19:21:16 +0200 |
commit | 86556ebfdbbd4b8e9edc9d3085ea21d6c0bae2e2 (patch) | |
tree | 9f8b4b752ed728ceff82dceef2f5605cb2a846a0 | |
parent | b5730ec01f02e542eb06161430aa5a7c2ede775f (diff) | |
download | Nim-86556ebfdbbd4b8e9edc9d3085ea21d6c0bae2e2.tar.gz |
compiler refactoring; use typesafe path handing; docgen: render symbols between modules
45 files changed, 817 insertions, 505 deletions
diff --git a/compiler/ccgmerge.nim b/compiler/ccgmerge.nim index 067a60c57..144a45816 100644 --- a/compiler/ccgmerge.nim +++ b/compiler/ccgmerge.nim @@ -12,7 +12,7 @@ import ast, astalgo, ropes, options, strutils, nimlexbase, msgs, cgendata, rodutils, - intsets, platform, llstream, tables, sighashes + intsets, platform, llstream, tables, sighashes, pathutils # Careful! Section marks need to contain a tabulator so that they cannot # be part of C string literals. @@ -226,7 +226,7 @@ proc processMergeInfo(L: var TBaseLexer, m: BModule) = when not defined(nimhygiene): {.pragma: inject.} -template withCFile(cfilename: string, body: untyped) = +template withCFile(cfilename: AbsoluteFile, body: untyped) = var s = llStreamOpen(cfilename, fmRead) if s == nil: return var L {.inject.}: TBaseLexer @@ -238,7 +238,7 @@ template withCFile(cfilename: string, body: untyped) = body closeBaseLexer(L) -proc readMergeInfo*(cfilename: string, m: BModule) = +proc readMergeInfo*(cfilename: AbsoluteFile, m: BModule) = ## reads the merge meta information into `m`. withCFile(cfilename): readKey(L, k) @@ -251,7 +251,7 @@ type f: TCFileSections p: TCProcSections -proc readMergeSections(cfilename: string, m: var TMergeSections) = +proc readMergeSections(cfilename: AbsoluteFile, m: var TMergeSections) = ## reads the merge sections into `m`. withCFile(cfilename): readKey(L, k) @@ -285,7 +285,7 @@ proc mergeRequired*(m: BModule): bool = #echo "not empty: ", i, " ", m.initProc.s[i] return true -proc mergeFiles*(cfilename: string, m: BModule) = +proc mergeFiles*(cfilename: AbsoluteFile, m: BModule) = ## merges the C file with the old version on hard disc. var old: TMergeSections readMergeSections(cfilename, old) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index dea8b1e8a..7a74d8a9b 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -14,7 +14,7 @@ import nversion, nimsets, msgs, std / sha1, bitsets, idents, types, ccgutils, os, ropes, math, passes, wordrecg, treetab, cgmeth, condsyms, rodutils, renderer, idgen, cgendata, ccgmerge, semfold, aliases, - lowerings, semparallel, tables, sets, ndi, lineinfos + lowerings, semparallel, tables, sets, ndi, lineinfos, pathutils import strutils except `%` # collides with ropes.`%` @@ -1064,7 +1064,8 @@ proc genFilenames(m: BModule): Rope = discard cgsym(m, "dbgRegisterFilename") result = nil for i in 0..<m.config.m.fileInfos.len: - result.addf("dbgRegisterFilename($1);$N", [m.config.m.fileInfos[i].projPath.makeCString]) + result.addf("dbgRegisterFilename($1);$N", + [m.config.m.fileInfos[i].projPath.string.makeCString]) proc genMainProc(m: BModule) = const @@ -1348,7 +1349,7 @@ proc initProcOptions(m: BModule): TOptions = let opts = m.config.options if sfSystemModule in m.module.flags: opts-{optStackTrace} else: opts -proc rawNewModule(g: BModuleList; module: PSym, filename: string): BModule = +proc rawNewModule(g: BModuleList; module: PSym, filename: AbsoluteFile): BModule = new(result) result.g = g result.tmpBase = rope("TM" & $hashOwner(module) & "_") @@ -1376,7 +1377,7 @@ proc rawNewModule(g: BModuleList; module: PSym, filename: string): BModule = incl result.flags, preventStackTrace excl(result.preInitProc.options, optStackTrace) let ndiName = if optCDebug in g.config.globalOptions: changeFileExt(completeCFilePath(g.config, filename), "ndi") - else: "" + else: AbsoluteFile"" open(result.ndi, ndiName, g.config) proc nullify[T](arr: var T) = @@ -1427,7 +1428,7 @@ proc resetCgenModules*(g: BModuleList) = for m in cgenModules(g): resetModule(m) proc rawNewModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule = - result = rawNewModule(g, module, toFullPath(conf, module.position.FileIndex)) + result = rawNewModule(g, module, AbsoluteFile toFullPath(conf, module.position.FileIndex)) proc newModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule = # we should create only one cgen module for each module sym @@ -1446,7 +1447,7 @@ proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = injectG() result = newModule(g, module, graph.config) if optGenIndex in graph.config.globalOptions and g.generatedHeader == nil: - let f = if graph.config.headerFile.len > 0: graph.config.headerFile + let f = if graph.config.headerFile.len > 0: AbsoluteFile graph.config.headerFile else: graph.config.projectFull g.generatedHeader = rawNewModule(g, module, changeFileExt(completeCFilePath(graph.config, f), hExt)) @@ -1477,9 +1478,9 @@ proc writeHeader(m: BModule) = if optUseNimNamespace in m.config.globalOptions: result.add closeNamespaceNim() result.addf("#endif /* $1 */$n", [guard]) if not writeRope(result, m.filename): - rawMessage(m.config, errCannotOpenFile, m.filename) + rawMessage(m.config, errCannotOpenFile, m.filename.string) -proc getCFile(m: BModule): string = +proc getCFile(m: BModule): AbsoluteFile = let ext = if m.compileToCpp: ".cpp" elif m.config.cmd == cmdCompileToOC or sfCompileToObjC in m.module.flags: ".m" @@ -1523,18 +1524,18 @@ proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool = if not equalsFile(code, cfile.cname): if isDefined(m.config, "nimdiff"): if fileExists(cfile.cname): - copyFile(cfile.cname, cfile.cname & ".backup") - echo "diff ", cfile.cname, ".backup ", cfile.cname + copyFile(cfile.cname.string, cfile.cname.string & ".backup") + echo "diff ", cfile.cname.string, ".backup ", cfile.cname.string else: - echo "new file ", cfile.cname + echo "new file ", cfile.cname.string if not writeRope(code, cfile.cname): - rawMessage(m.config, errCannotOpenFile, cfile.cname) + rawMessage(m.config, errCannotOpenFile, cfile.cname.string) return - if existsFile(cfile.obj) and os.fileNewer(cfile.obj, cfile.cname): + if fileExists(cfile.obj) and os.fileNewer(cfile.obj.string, cfile.cname.string): result = false else: if not writeRope(code, cfile.cname): - rawMessage(m.config, errCannotOpenFile, cfile.cname) + rawMessage(m.config, errCannotOpenFile, cfile.cname.string) # We need 2 different logics here: pending modules (including # 'nim__dat') may require file merging for the combination of dead code @@ -1570,14 +1571,14 @@ proc writeModule(m: BModule, pending: bool) = finishTypeDescriptions(m) var code = genModule(m, cf) if not writeRope(code, cfile): - rawMessage(m.config, errCannotOpenFile, cfile) + rawMessage(m.config, errCannotOpenFile, cfile.string) addFileToCompile(m.config, cf) else: # Consider: first compilation compiles ``system.nim`` and produces # ``system.c`` but then compilation fails due to an error. This means # that ``system.o`` is missing, so we need to call the C compiler for it: var cf = Cfile(cname: cfile, obj: completeCFilePath(m.config, toObjFile(m.config, cfile)), flags: {}) - if not existsFile(cf.obj): cf.flags = {CfileFlag.Cached} + if not fileExists(cf.obj): cf.flags = {CfileFlag.Cached} addFileToCompile(m.config, cf) close(m.ndi) @@ -1592,7 +1593,7 @@ proc updateCachedModule(m: BModule) = var code = genModule(m, cf) if not writeRope(code, cfile): - rawMessage(m.config, errCannotOpenFile, cfile) + rawMessage(m.config, errCannotOpenFile, cfile.string) else: cf.flags = {CfileFlag.Cached} addFileToCompile(m.config, cf) diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim index 56dbd65a2..406ee050f 100644 --- a/compiler/cgendata.nim +++ b/compiler/cgendata.nim @@ -11,7 +11,7 @@ import ast, astalgo, ropes, passes, options, intsets, platform, sighashes, - tables, ndi, lineinfos + tables, ndi, lineinfos, pathutils from modulegraphs import ModuleGraph @@ -136,8 +136,8 @@ type s*: TCFileSections # sections of the C file flags*: set[Codegenflag] module*: PSym - filename*: string - cfilename*: string # filename of the module (including path, + filename*: AbsoluteFile + cfilename*: AbsoluteFile # filename of the module (including path, # without extension) tmpBase*: Rope # base for temp identifier generation typeCache*: TypeCache # cache the generated types diff --git a/compiler/cmdlinehelper.nim b/compiler/cmdlinehelper.nim index 9d2334af5..8bd073314 100644 --- a/compiler/cmdlinehelper.nim +++ b/compiler/cmdlinehelper.nim @@ -1,10 +1,17 @@ -## Helpers for binaries that use compiler passes, eg: nim, nimsuggest, nimfix +# +# +# The Nim Compiler +# (c) Copyright 2018 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# -# TODO: nimfix should use this; currently out of sync +## Helpers for binaries that use compiler passes, eg: nim, nimsuggest, nimfix import - compiler/[options, idents, nimconf, scriptconfig, extccomp, commands, msgs, lineinfos, modulegraphs, condsyms], - std/os + options, idents, nimconf, scriptconfig, extccomp, commands, msgs, + lineinfos, modulegraphs, condsyms, os, pathutils type NimProg* = ref object @@ -21,27 +28,27 @@ proc processCmdLineAndProjectPath*(self: NimProg, conf: ConfigRef) = self.processCmdLine(passCmd1, "", conf) if self.supportsStdinFile and conf.projectName == "-": conf.projectName = "stdinfile" - conf.projectFull = "stdinfile" - conf.projectPath = canonicalizePath(conf, getCurrentDir()) + conf.projectFull = AbsoluteFile "stdinfile" + conf.projectPath = AbsoluteDir getCurrentDir() conf.projectIsStdin = true elif conf.projectName != "": try: - conf.projectFull = canonicalizePath(conf, conf.projectName) + conf.projectFull = canonicalizePath(conf, AbsoluteFile conf.projectName) except OSError: - conf.projectFull = conf.projectName + conf.projectFull = AbsoluteFile conf.projectName let p = splitFile(conf.projectFull) - let dir = if p.dir.len > 0: p.dir else: getCurrentDir() - conf.projectPath = canonicalizePath(conf, dir) + let dir = if p.dir.isEmpty: AbsoluteDir getCurrentDir() else: p.dir + conf.projectPath = AbsoluteDir canonicalizePath(conf, AbsoluteFile dir) conf.projectName = p.name else: - conf.projectPath = canonicalizePath(conf, getCurrentDir()) + conf.projectPath = AbsoluteDir canonicalizePath(conf, AbsoluteFile getCurrentDir()) proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: ConfigRef): bool = loadConfigs(DefaultConfig, cache, conf) # load all config files if self.suggestMode: conf.command = "nimsuggest" - proc runNimScriptIfExists(path: string)= + proc runNimScriptIfExists(path: AbsoluteFile)= if fileExists(path): runNimScript(cache, path, freshDefines = false, conf) @@ -53,8 +60,8 @@ proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: Confi runNimScriptIfExists(getUserConfigPath(DefaultConfigNims)) if optSkipParentConfigFiles notin conf.globalOptions: - for dir in parentDirs(conf.projectPath, fromRoot = true, inclusive = false): - runNimScriptIfExists(dir / DefaultConfigNims) + for dir in parentDirs(conf.projectPath.string, fromRoot = true, inclusive = false): + runNimScriptIfExists(AbsoluteDir(dir) / DefaultConfigNims) if optSkipProjConfigFile notin conf.globalOptions: runNimScriptIfExists(conf.projectPath / DefaultConfigNims) @@ -63,10 +70,10 @@ proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: Confi if not self.suggestMode: runNimScriptIfExists(scriptFile) # 'nim foo.nims' means to just run the NimScript file and do nothing more: - if fileExists(scriptFile) and scriptFile.cmpPaths(conf.projectFull) == 0: + if fileExists(scriptFile) and scriptFile == conf.projectFull: return false else: - if scriptFile.cmpPaths(conf.projectFull) != 0: + if scriptFile != conf.projectFull: runNimScriptIfExists(scriptFile) else: # 'nimsuggest foo.nims' means to just auto-complete the NimScript file diff --git a/compiler/commands.nim b/compiler/commands.nim index b47ccf610..fe820a589 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -26,7 +26,8 @@ bootSwitch(usedNoGC, defined(nogc), "--gc:none") import os, msgs, options, nversion, condsyms, strutils, extccomp, platform, - wordrecg, parseutils, nimblecmd, idents, parseopt, sequtils, lineinfos + wordrecg, parseutils, nimblecmd, idents, parseopt, sequtils, lineinfos, + pathutils # but some have deps to imported modules. Yay. bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc") @@ -208,7 +209,7 @@ proc processSpecificNote*(arg: string, state: TSpecialWord, pass: TCmdLinePass, proc processCompile(conf: ConfigRef; filename: string) = var found = findFile(conf, filename) - if found == "": found = filename + if found.isEmpty: found = AbsoluteFile filename extccomp.addExternalFileToCompile(conf, found) const @@ -292,31 +293,32 @@ proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool else: invalidCmdLineOption(conf, passCmd1, switch, info) proc processPath(conf: ConfigRef; path: string, info: TLineInfo, - notRelativeToProj = false): string = + notRelativeToProj = false): AbsoluteDir = let p = if os.isAbsolute(path) or '$' in path: path elif notRelativeToProj: getCurrentDir() / path else: - conf.projectPath / path + conf.projectPath.string / path try: - result = pathSubs(conf, p, toFullPath(conf, info).splitFile().dir) + result = AbsoluteDir pathSubs(conf, p, toFullPath(conf, info).splitFile().dir) except ValueError: localError(conf, info, "invalid path: " & p) - result = p + result = AbsoluteDir p -proc processCfgPath(conf: ConfigRef; path: string, info: TLineInfo): string = - let path = if path[0] == '"': strutils.unescape(path) else: path +proc processCfgPath(conf: ConfigRef; path: string, info: TLineInfo): AbsoluteDir = + let path = if path.len > 0 and path[0] == '"': strutils.unescape(path) + else: path let basedir = toFullPath(conf, info).splitFile().dir let p = if os.isAbsolute(path) or '$' in path: path else: basedir / path try: - result = pathSubs(conf, p, basedir) + result = AbsoluteDir pathSubs(conf, p, basedir) except ValueError: localError(conf, info, "invalid path: " & p) - result = p + result = AbsoluteDir p const errInvalidNumber = "$1 is not a valid number" @@ -331,9 +333,9 @@ proc trackDirty(conf: ConfigRef; arg: string, info: TLineInfo) = if parseUtils.parseInt(a[3], column) <= 0: localError(conf, info, errInvalidNumber % a[2]) - let dirtyOriginalIdx = fileInfoIdx(conf, a[1]) + let dirtyOriginalIdx = fileInfoIdx(conf, AbsoluteFile a[1]) if dirtyOriginalIdx.int32 >= 0: - msgs.setDirtyFile(conf, dirtyOriginalIdx, a[0]) + msgs.setDirtyFile(conf, dirtyOriginalIdx, AbsoluteFile a[0]) conf.m.trackPos = newLineInfo(dirtyOriginalIdx, line, column) @@ -345,7 +347,7 @@ proc track(conf: ConfigRef; arg: string, info: TLineInfo) = localError(conf, info, errInvalidNumber % a[1]) if parseUtils.parseInt(a[2], column) <= 0: localError(conf, info, errInvalidNumber % a[2]) - conf.m.trackPos = newLineInfo(conf, a[0], line, column) + conf.m.trackPos = newLineInfo(conf, AbsoluteFile a[0], line, column) proc dynlibOverride(conf: ConfigRef; switch, arg: string, pass: TCmdLinePass, info: TLineInfo) = if pass in {passCmd2, passPP}: @@ -359,14 +361,16 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; case switch.normalize of "path", "p": expectArg(conf, switch, arg, pass, info) - addPath(conf, if pass == passPP: processCfgPath(conf, arg, info) else: processPath(conf, arg, info), info) + addPath(conf, if pass == passPP: processCfgPath(conf, arg, info) + else: processPath(conf, arg, info), info) of "nimblepath", "babelpath": # keep the old name for compat if pass in {passCmd2, passPP} and optNoNimblePath notin conf.globalOptions: expectArg(conf, switch, arg, pass, info) var path = processPath(conf, arg, info, notRelativeToProj=true) - let nimbleDir = getEnv("NIMBLE_DIR") - if nimbleDir.len > 0 and pass == passPP: path = nimbleDir / "pkgs" + let nimbleDir = AbsoluteDir getEnv("NIMBLE_DIR") + if not nimbleDir.isEmpty and pass == passPP: + path = nimbleDir / RelativeDir"pkgs" nimblePath(conf, path, info) of "nonimblepath", "nobabelpath": expectNoArg(conf, switch, arg, pass, info) @@ -374,20 +378,14 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; of "excludepath": expectArg(conf, switch, arg, pass, info) let path = processPath(conf, arg, info) - - conf.searchPaths.keepItIf(cmpPaths(it, path) != 0) - conf.lazyPaths.keepItIf(cmpPaths(it, path) != 0) - - if (len(path) > 0) and (path[len(path) - 1] == DirSep): - let strippedPath = path[0 .. (len(path) - 2)] - conf.searchPaths.keepItIf(cmpPaths(it, strippedPath) != 0) - conf.lazyPaths.keepItIf(cmpPaths(it, strippedPath) != 0) + conf.searchPaths.keepItIf(it != path) + conf.lazyPaths.keepItIf(it != path) of "nimcache": expectArg(conf, switch, arg, pass, info) conf.nimcacheDir = processPath(conf, arg, info, true) of "out", "o": expectArg(conf, switch, arg, pass, info) - conf.outFile = arg + conf.outFile = AbsoluteFile arg of "docseesrcurl": expectArg(conf, switch, arg, pass, info) conf.docSeeSrcUrl = arg @@ -411,7 +409,8 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; if pass in {passCmd2, passPP}: processCompile(conf, arg) of "link": expectArg(conf, switch, arg, pass, info) - if pass in {passCmd2, passPP}: addExternalFileToLink(conf, arg) + if pass in {passCmd2, passPP}: + addExternalFileToLink(conf, AbsoluteFile arg) of "debuginfo": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optCDebug) @@ -581,7 +580,8 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; if pass in {passCmd2, passPP}: conf.cLibs.add processPath(conf, arg, info) of "clib": expectArg(conf, switch, arg, pass, info) - if pass in {passCmd2, passPP}: conf.cLinkedLibs.add processPath(conf, arg, info) + if pass in {passCmd2, passPP}: + conf.cLinkedLibs.add processPath(conf, arg, info).string of "header": if conf != nil: conf.headerFile = arg incl(conf.globalOptions, optGenIndex) @@ -742,7 +742,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; if strutils.find(switch, '.') >= 0: options.setConfigVar(conf, switch, arg) else: invalidCmdLineOption(conf, pass, switch, info) -template gCmdLineInfo*(): untyped = newLineInfo(config, "command line", 1, 1) +template gCmdLineInfo*(): untyped = newLineInfo(config, AbsoluteFile"command line", 1, 1) proc processCommand*(switch: string, pass: TCmdLinePass; config: ConfigRef) = var cmd, arg: string diff --git a/compiler/depends.nim b/compiler/depends.nim index d0a1139ef..c26593ea5 100644 --- a/compiler/depends.nim +++ b/compiler/depends.nim @@ -10,7 +10,8 @@ # This module implements a dependency file generator. import - os, options, ast, astalgo, msgs, ropes, idents, passes, modulepaths + os, options, ast, astalgo, msgs, ropes, idents, passes, modulepaths, + pathutils from modulegraphs import ModuleGraph @@ -45,10 +46,10 @@ proc addDotDependency(c: PPassContext, n: PNode): PNode = else: discard -proc generateDot*(graph: ModuleGraph; project: string) = +proc generateDot*(graph: ModuleGraph; project: AbsoluteFile) = let b = Backend(graph.backend) discard writeRope("digraph $1 {$n$2}$n" % [ - rope(changeFileExt(extractFilename(project), "")), b.dotGraph], + rope(project.splitFile.name), b.dotGraph], changeFileExt(project, "dot")) proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 6f26bcf10..428fcbfe3 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -16,7 +16,8 @@ import wordrecg, syntaxes, renderer, lexer, packages/docutils/rstast, packages/docutils/rst, packages/docutils/rstgen, packages/docutils/highlite, sempass2, json, xmltree, cgi, - typesrenderer, astalgo, modulepaths, lineinfos, sequtils, intsets + typesrenderer, astalgo, modulepaths, lineinfos, sequtils, intsets, + pathutils type TSections = array[TSymKind, Rope] @@ -34,6 +35,8 @@ type exampleCounter: int emitted: IntSet # we need to track which symbols have been emitted # already. See bug #3655 + destFile*: AbsoluteFile + thisDir*: AbsoluteDir PDoc* = ref TDocumentor ## Alias to type less. @@ -48,12 +51,12 @@ proc whichType(d: PDoc; n: PNode): PSym = proc attachToType(d: PDoc; p: PSym): PSym = let params = p.ast.sons[paramsPos] - # first check the first parameter, then the return type, - # then the other parameter: template check(i) = result = whichType(d, params[i]) if result != nil: return result + # first check the first parameter, then the return type, + # then the other parameter: if params.len > 1: check(1) if params.len > 0: check(0) for i in 2..<params.len: check(i) @@ -74,10 +77,10 @@ template declareClosures = of mwUnknownSubstitution: k = warnUnknownSubstitutionX of mwUnsupportedLanguage: k = warnLanguageXNotSupported of mwUnsupportedField: k = warnFieldXNotSupported - globalError(conf, newLineInfo(conf, filename, line, col), k, arg) + globalError(conf, newLineInfo(conf, AbsoluteFile filename, line, col), k, arg) proc docgenFindFile(s: string): string {.procvar.} = - result = options.findFile(conf, s) + result = options.findFile(conf, s).string if result.len == 0: result = getCurrentDir() / s if not existsFile(result): result = "" @@ -90,13 +93,24 @@ proc parseRst(text, filename: string, result = rstParse(text, filename, line, column, hasToc, rstOptions, docgenFindFile, compilerMsgHandler) -proc newDocumentor*(filename: string; cache: IdentCache; conf: ConfigRef): PDoc = +proc getOutFile2(conf: ConfigRef; filename: RelativeFile, + ext: string, dir: RelativeDir): AbsoluteFile = + if optWholeProject in conf.globalOptions: + # This is correct, for 'nim doc --project' we interpret the '--out' option as an + # absolute directory, not as a filename! + let d = if conf.outFile.isEmpty: conf.projectPath / dir else: AbsoluteDir(conf.outFile) + createDir(d) + result = d / changeFileExt(filename, ext) + else: + result = getOutFile(conf, filename, ext) + +proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef): PDoc = declareClosures() new(result) result.conf = conf result.cache = cache initRstGenerator(result[], (if conf.cmd != cmdRst2tex: outHtml else: outLatex), - conf.configVars, filename, {roSupportRawDirective}, + conf.configVars, filename.string, {roSupportRawDirective}, docgenFindFile, compilerMsgHandler) if conf.configVars.hasKey("doc.googleAnalytics"): @@ -120,8 +134,12 @@ proc newDocumentor*(filename: string; cache: IdentCache; conf: ConfigRef): PDoc result.jArray = newJArray() initStrTable result.types result.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; status: int; content: string) = - localError(conf, newLineInfo(conf, d.filename, -1, -1), warnUser, "only 'rst2html' supports the ':test:' attribute") + localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1), + warnUser, "only 'rst2html' supports the ':test:' attribute") result.emitted = initIntSet() + result.destFile = getOutFile2(conf, relativeTo(filename, conf.projectPath), + HtmlExt, RelativeDir"htmldocs") + result.thisDir = result.destFile.splitFile.dir proc dispA(conf: ConfigRef; dest: var Rope, xml, tex: string, args: openArray[Rope]) = if conf.cmd != cmdRst2tex: addf(dest, xml, args) @@ -227,6 +245,10 @@ proc getPlainDocstring(n: PNode): string = result = getPlainDocstring(n.sons[i]) if result.len > 0: return +proc belongsToPackage(conf: ConfigRef; module: PSym): bool = + result = module.kind == skModule and module.owner != nil and + module.owner.id == conf.mainPackageId + proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRenderFlags = {}) = var r: TSrcGen var literal = "" @@ -259,8 +281,22 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe dispA(d.conf, result, "<span class=\"FloatNumber\">$1</span>", "\\spanFloatNumber{$1}", [rope(esc(d.target, literal))]) of tkSymbol: - dispA(d.conf, result, "<span class=\"Identifier\">$1</span>", - "\\spanIdentifier{$1}", [rope(esc(d.target, literal))]) + let s = getTokSym(r) + if s != nil and s.kind == skType and sfExported in s.flags and + s.owner != nil and belongsToPackage(d.conf, s.owner) and + d.target == outHtml: + + let full = AbsoluteFile toFullPath(d.conf, FileIndex s.owner.position) + let tmp = getOutFile2(d.conf, full.relativeTo(d.conf.projectPath), + HtmlExt, RelativeDir"htmldocs") + + let external = tmp.relativeTo(d.thisDir, '/') + result.addf "<a href=\"$1#$2\"><span class=\"Identifier\">$3</span></a>", + [rope changeFileExt(external, "html").string, rope literal, + rope(esc(d.target, literal))] + else: + dispA(d.conf, result, "<span class=\"Identifier\">$1</span>", + "\\spanIdentifier{$1}", [rope(esc(d.target, literal))]) of tkSpaces, tkInvalid: add(result, literal) of tkCurlyDotLe: @@ -290,23 +326,24 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe proc testExample(d: PDoc; ex: PNode) = if d.conf.errorCounter > 0: return - let outputDir = d.conf.getNimcacheDir / "runnableExamples" + let outputDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples" createDir(outputDir) inc d.exampleCounter - let outp = outputDir / extractFilename(d.filename.changeFileExt"" & - "_examples" & $d.exampleCounter & ".nim") + let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" & + "_examples" & $d.exampleCounter & ".nim")) #let nimcache = outp.changeFileExt"" & "_nimcache" - renderModule(ex, d.filename, outp, conf = d.conf) + renderModule(ex, d.filename, outp.string, conf = d.conf) let backend = if isDefined(d.conf, "js"): "js" elif isDefined(d.conf, "cpp"): "cpp" elif isDefined(d.conf, "objc"): "objc" else: "c" if os.execShellCmd(os.getAppFilename() & " " & backend & - " --nimcache:" & outputDir & " -r " & outp) != 0: - quit "[Examples] failed: see " & outp + " --nimcache:" & quoteShell(outputDir) & + " -r " & quoteShell(outp)) != 0: + quit "[Examples] failed: see " & outp.string else: # keep generated source file `outp` to allow inspection. - rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp]) + rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp.string]) removeFile(outp.changeFileExt(ExeExt)) proc extractImports(n: PNode; result: PNode) = @@ -449,10 +486,8 @@ proc newUniquePlainSymbol(d: PDoc, original: string): string = result = original d.seenSymbols[original] = "" return - # Iterate over possible numeric variants of the original name. var count = 2 - while true: result = original & "_" & $count if not d.seenSymbols.hasKey(result): @@ -460,7 +495,6 @@ proc newUniquePlainSymbol(d: PDoc, original: string): string = break count += 1 - proc complexName(k: TSymKind, n: PNode, baseName: string): string = ## Builds a complex unique href name for the node. ## @@ -482,11 +516,9 @@ proc complexName(k: TSymKind, n: PNode, baseName: string): string = of skTemplate: result.add(".t" & defaultParamSeparator) of skConverter: result.add(".c" & defaultParamSeparator) else: discard - if len(n) > paramsPos and n[paramsPos].kind == nkFormalParams: result.add(renderParamTypes(n[paramsPos])) - proc isCallable(n: PNode): bool = ## Returns true if `n` contains a callable node. case n.kind @@ -495,7 +527,6 @@ proc isCallable(n: PNode): bool = else: result = false - proc docstringSummary(rstText: string): string = ## Returns just the first line or a brief chunk of text from a rst string. ## @@ -523,7 +554,6 @@ proc docstringSummary(rstText: string): string = result.delete(pos, last) result.add("…") - proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = if not isVisible(d, nameNode): return let @@ -545,8 +575,8 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = break plainName.add(literal) - # Render the HTML hyperlink. - nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments, renderDocComments}) + nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments, + renderDocComments, renderSyms}) inc(d.id) let @@ -563,16 +593,18 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = var seeSrcRope: Rope = nil let docItemSeeSrc = getConfigVar(d.conf, "doc.item.seesrc") if docItemSeeSrc.len > 0: - let cwd = canonicalizePath(d.conf, getCurrentDir()) - var path = toFullPath(d.conf, n.info) - if path.startsWith(cwd): - path = path[cwd.len+1 .. ^1].replace('\\', '/') + let path = relativeTo(AbsoluteFile toFullPath(d.conf, n.info), d.conf.projectPath, '/') + when false: + let cwd = canonicalizePath(d.conf, getCurrentDir()) + var path = toFullPath(d.conf, n.info) + if path.startsWith(cwd): + path = path[cwd.len+1 .. ^1].replace('\\', '/') let gitUrl = getConfigVar(d.conf, "git.url") if gitUrl.len > 0: let commit = getConfigVar(d.conf, "git.commit", "master") let develBranch = getConfigVar(d.conf, "git.devel", "devel") dispA(d.conf, seeSrcRope, "$1", "", [ropeFormatNamedVars(d.conf, docItemSeeSrc, - ["path", "line", "url", "commit", "devel"], [rope path, + ["path", "line", "url", "commit", "devel"], [rope path.string, rope($n.info.line), rope gitUrl, rope commit, rope develBranch])]) add(d.section[k], ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.item"), @@ -611,9 +643,7 @@ proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode = name = getName(d, nameNode) comm = $genRecComment(d, n) r: TSrcGen - initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments}) - result = %{ "name": %name, "type": %($k), "line": %n.info.line.int, "col": %n.info.col} if comm.len > 0: @@ -626,7 +656,6 @@ proc checkForFalse(n: PNode): bool = proc traceDeps(d: PDoc, it: PNode) = const k = skModule - if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket: let sep = it[0] let dir = it[1] @@ -637,11 +666,16 @@ proc traceDeps(d: PDoc, it: PNode) = for x in it[2]: a.sons[2] = x traceDeps(d, a) - else: + elif it.kind == nkSym and belongsToPackage(d.conf, it.sym): + let full = AbsoluteFile toFullPath(d.conf, FileIndex it.sym.position) + let tmp = getOutFile2(d.conf, full.relativeTo(d.conf.projectPath), HtmlExt, + RelativeDir"htmldocs") + let external = relativeTo(tmp, d.thisDir, '/') if d.section[k] != nil: add(d.section[k], ", ") dispA(d.conf, d.section[k], - "<a class=\"reference external\" href=\"$1.html\">$1</a>", - "$1", [rope(splitFile(getModuleName(d.conf, it)).name)]) + "<a class=\"reference external\" href=\"$2\">$1</a>", + "$1", [rope esc(d.target, it.sym.name.s), + rope changeFileExt(external, "html").string]) proc generateDoc*(d: PDoc, n: PNode) = case n.kind @@ -829,29 +863,23 @@ proc genOutFile(d: PDoc): Rope = proc generateIndex*(d: PDoc) = if optGenIndex in d.conf.globalOptions: - writeIndexFile(d[], splitFile(d.conf.outFile).dir / - splitFile(d.filename).name & IndexExt) - -proc getOutFile2(conf: ConfigRef; filename, ext, dir: string): string = - if optWholeProject in conf.globalOptions: - let d = if conf.outFile != "": conf.outFile else: dir - createDir(d) - result = d / changeFileExt(filename, ext) - else: - result = getOutFile(conf, filename, ext) + let dest = getOutFile2(d.conf, relativeTo(AbsoluteFile d.filename, d.conf.projectPath), + IndexExt, RelativeDir"index") + writeIndexFile(d[], dest.string) -proc writeOutput*(d: PDoc, filename, outExt: string, useWarning = false) = +proc writeOutput*(d: PDoc, useWarning = false) = var content = genOutFile(d) if optStdout in d.conf.globalOptions: writeRope(stdout, content) else: - let outfile = getOutFile2(d.conf, filename, outExt, "htmldocs") - createDir(outfile.parentDir) + template outfile: untyped = d.destFile + #let outfile = getOutFile2(d.conf, shortenDir(d.conf, filename), outExt, "htmldocs") + createDir(outfile.splitFile.dir) if not writeRope(content, outfile): - rawMessage(d.conf, if useWarning: warnCannotOpenFile else: errCannotOpenFile, outfile) + rawMessage(d.conf, if useWarning: warnCannotOpenFile else: errCannotOpenFile, + outfile.string) -proc writeOutputJson*(d: PDoc, filename, outExt: string, - useWarning = false) = +proc writeOutputJson*(d: PDoc, useWarning = false) = let content = %*{"orig": d.filename, "nimble": getPackageName(d.conf, d.filename), "entries": d.jArray} @@ -859,8 +887,7 @@ proc writeOutputJson*(d: PDoc, filename, outExt: string, write(stdout, $content) else: var f: File - if open(f, getOutFile2(d.conf, splitFile(filename).name, - outExt, "jsondocs"), fmWrite): + if open(f, d.destFile.string, fmWrite): write(f, $content) close(f) else: @@ -872,26 +899,27 @@ proc commandDoc*(cache: IdentCache, conf: ConfigRef) = var d = newDocumentor(conf.projectFull, cache, conf) d.hasToc = true generateDoc(d, ast) - writeOutput(d, conf.projectFull, HtmlExt) + writeOutput(d) generateIndex(d) -proc commandRstAux(cache: IdentCache, conf: ConfigRef; filename, outExt: string) = +proc commandRstAux(cache: IdentCache, conf: ConfigRef; + filename: AbsoluteFile, outExt: string) = var filen = addFileExt(filename, "txt") var d = newDocumentor(filen, cache, conf) d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; status: int; content: string) = - var outp: string + var outp: AbsoluteFile if filename.len == 0: inc(d.id) let nameOnly = splitFile(d.filename).name - let subdir = getNimcacheDir(conf) / nameOnly + let subdir = getNimcacheDir(conf) / RelativeDir(nameOnly) createDir(subdir) - outp = subdir / (nameOnly & "_snippet_" & $d.id & ".nim") + outp = subdir / RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim") elif isAbsolute(filename): - outp = filename + outp = AbsoluteFile filename else: # Nim's convention: every path is relative to the file it was written in: - outp = splitFile(d.filename).dir / filename + outp = splitFile(d.filename).dir.AbsoluteDir / RelativeFile(filename) writeFile(outp, content) let cmd = cmd % quoteShell(outp) rawMessage(conf, hintExecuting, cmd) @@ -899,14 +927,12 @@ proc commandRstAux(cache: IdentCache, conf: ConfigRef; filename, outExt: string) rawMessage(conf, errGenerated, "executing of external program failed: " & cmd) d.isPureRst = true - var rst = parseRst(readFile(filen), filen, 0, 1, d.hasToc, + var rst = parseRst(readFile(filen.string), filen.string, 0, 1, d.hasToc, {roSupportRawDirective}, conf) var modDesc = newStringOfCap(30_000) - #d.modDesc = newMutableRope(30_000) renderRstToOut(d[], rst, modDesc) - #freezeMutableRope(d.modDesc) d.modDesc = rope(modDesc) - writeOutput(d, filename, outExt) + writeOutput(d) generateIndex(d) proc commandRst2Html*(cache: IdentCache, conf: ConfigRef) = @@ -928,9 +954,9 @@ proc commandJson*(cache: IdentCache, conf: ConfigRef) = writeRope(stdout, content) else: #echo getOutFile(gProjectFull, JsonExt) - let filename = getOutFile(conf, conf.projectFull, JsonExt) + let filename = getOutFile(conf, RelativeFile conf.projectName, JsonExt) if not writeRope(content, filename): - rawMessage(conf, errCannotOpenFile, filename) + rawMessage(conf, errCannotOpenFile, filename.string) proc commandTags*(cache: IdentCache, conf: ConfigRef) = var ast = parseFile(conf.projectMainIdx, cache, conf) @@ -945,12 +971,12 @@ proc commandTags*(cache: IdentCache, conf: ConfigRef) = writeRope(stdout, content) else: #echo getOutFile(gProjectFull, TagsExt) - let filename = getOutFile(conf, conf.projectFull, TagsExt) + let filename = getOutFile(conf, RelativeFile conf.projectName, TagsExt) if not writeRope(content, filename): - rawMessage(conf, errCannotOpenFile, filename) + rawMessage(conf, errCannotOpenFile, filename.string) proc commandBuildIndex*(cache: IdentCache, conf: ConfigRef) = - var content = mergeIndexes(conf.projectFull).rope + var content = mergeIndexes(conf.projectFull.string).rope let code = ropeFormatNamedVars(conf, getConfigVar(conf, "doc.file"), ["title", "tableofcontents", "moduledesc", "date", "time", @@ -958,6 +984,6 @@ proc commandBuildIndex*(cache: IdentCache, conf: ConfigRef) = ["Index".rope, nil, nil, rope(getDateStr()), rope(getClockStr()), content, nil, nil, nil]) # no analytics because context is not available - let filename = getOutFile(conf, "theindex", HtmlExt) + let filename = getOutFile(conf, RelativeFile"theindex", HtmlExt) if not writeRope(code, filename): - rawMessage(conf, errCannotOpenFile, filename) + rawMessage(conf, errCannotOpenFile, filename.string) diff --git a/compiler/docgen2.nim b/compiler/docgen2.nim index 22fef0d47..b6ef0275d 100644 --- a/compiler/docgen2.nim +++ b/compiler/docgen2.nim @@ -11,7 +11,8 @@ # semantic checking. import - os, options, ast, astalgo, msgs, ropes, idents, passes, docgen, lineinfos + os, options, ast, astalgo, msgs, ropes, idents, passes, docgen, lineinfos, + pathutils from modulegraphs import ModuleGraph @@ -38,11 +39,11 @@ template closeImpl(body: untyped) {.dirty.} = proc close(graph: ModuleGraph; p: PPassContext, n: PNode): PNode = closeImpl: - writeOutput(g.doc, toFullPath(graph.config, FileIndex g.module.position), HtmlExt, useWarning) + writeOutput(g.doc, useWarning) proc closeJson(graph: ModuleGraph; p: PPassContext, n: PNode): PNode = closeImpl: - writeOutputJson(g.doc, toFullPath(graph.config, FileIndex g.module.position), ".json", useWarning) + writeOutputJson(g.doc, useWarning) proc processNode(c: PPassContext, n: PNode): PNode = result = n @@ -60,7 +61,8 @@ proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = var g: PGen new(g) g.module = module - var d = newDocumentor(toFullPath(graph.config, FileIndex module.position), graph.cache, graph.config) + var d = newDocumentor(AbsoluteFile toFullPath(graph.config, FileIndex module.position), + graph.cache, graph.config) d.hasToc = true g.doc = d result = g diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index 16b0d614d..69698ae09 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -14,7 +14,7 @@ import ropes, os, strutils, osproc, platform, condsyms, options, msgs, - lineinfos, std / sha1, streams + lineinfos, std / sha1, streams, pathutils type TInfoCCProp* = enum # properties of the C compiler: @@ -429,12 +429,13 @@ proc initVars*(conf: ConfigRef) = if len(conf.ccompilerpath) == 0: conf.ccompilerpath = getConfigVar(conf, conf.cCompiler, ".path") -proc completeCFilePath*(conf: ConfigRef; cfile: string, createSubDir: bool = true): string = +proc completeCFilePath*(conf: ConfigRef; cfile: AbsoluteFile, + createSubDir: bool = true): AbsoluteFile = result = completeGeneratedFilePath(conf, cfile, createSubDir) -proc toObjFile*(conf: ConfigRef; filename: string): string = +proc toObjFile*(conf: ConfigRef; filename: AbsoluteFile): AbsoluteFile = # Object file for compilation - result = filename & "." & CC[conf.cCompiler].objExt + result = AbsoluteFile(filename.string & "." & CC[conf.cCompiler].objExt) proc addFileToCompile*(conf: ConfigRef; cf: Cfile) = conf.toCompile.add(cf) @@ -447,8 +448,8 @@ proc resetCompilationLists*(conf: ConfigRef) = # Maybe we can do that in checkDep on the other hand? conf.externalToLink.setLen 0 -proc addExternalFileToLink*(conf: ConfigRef; filename: string) = - conf.externalToLink.insert(filename, 0) +proc addExternalFileToLink*(conf: ConfigRef; filename: AbsoluteFile) = + conf.externalToLink.insert(filename.string, 0) proc execWithEcho(conf: ConfigRef; cmd: string, msg = hintExecuting): int = rawMessage(conf, msg, cmd) @@ -459,14 +460,15 @@ proc execExternalProgram*(conf: ConfigRef; cmd: string, msg = hintExecuting) = rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" % cmd) -proc generateScript(conf: ConfigRef; projectFile: string, script: Rope) = - let (dir, name, ext) = splitFile(projectFile) - let filename = getNimcacheDir(conf) / addFileExt("compile_" & name, - platform.OS[conf.target.targetOS].scriptExt) +proc generateScript(conf: ConfigRef; projectFile: AbsoluteFile, script: Rope) = + let (_, name, _) = splitFile(projectFile) + let filename = getNimcacheDir(conf) / RelativeFile(addFileExt("compile_" & name, + platform.OS[conf.target.targetOS].scriptExt)) if writeRope(script, filename): - copyFile(conf.libpath / "nimbase.h", getNimcacheDir(conf) / "nimbase.h") + copyFile(conf.libpath / RelativeFile"nimbase.h", + getNimcacheDir(conf) / RelativeFile"nimbase.h") else: - rawMessage(conf, errGenerated, "could not write to file: " & filename) + rawMessage(conf, errGenerated, "could not write to file: " & filename.string) proc getOptSpeed(conf: ConfigRef; c: TSystemCC): string = result = getConfigVar(conf, c, ".options.speed") @@ -490,7 +492,7 @@ proc noAbsolutePaths(conf: ConfigRef): bool {.inline.} = # `optGenMapping` is included here for niminst. result = conf.globalOptions * {optGenScript, optGenMapping} != {} -proc cFileSpecificOptions(conf: ConfigRef; cfilename: string): string = +proc cFileSpecificOptions(conf: ConfigRef; cfilename: AbsoluteFile): string = result = conf.compileOptions for option in conf.compileOptionsCmd: if strutils.find(result, option, 0) < 0: @@ -513,7 +515,7 @@ proc cFileSpecificOptions(conf: ConfigRef; cfilename: string): string = if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key)) proc getCompileOptions(conf: ConfigRef): string = - result = cFileSpecificOptions(conf, "__dummy__") + result = cFileSpecificOptions(conf, AbsoluteFile"__dummy__") proc getLinkOptions(conf: ConfigRef): string = result = conf.linkOptions & " " & conf.linkOptionsCmd & " " @@ -526,8 +528,8 @@ proc needsExeExt(conf: ConfigRef): bool {.inline.} = result = (optGenScript in conf.globalOptions and conf.target.targetOS == osWindows) or (conf.target.hostOS == osWindows) -proc getCompilerExe(conf: ConfigRef; compiler: TSystemCC; cfile: string): string = - result = if conf.cmd == cmdCompileToCpp and not cfile.endsWith(".c"): +proc getCompilerExe(conf: ConfigRef; compiler: TSystemCC; cfile: AbsoluteFile): string = + result = if conf.cmd == cmdCompileToCpp and not cfile.string.endsWith(".c"): CC[compiler].cppCompiler else: CC[compiler].compilerExe @@ -539,7 +541,7 @@ proc getCompilerExe(conf: ConfigRef; compiler: TSystemCC; cfile: string): string proc getLinkerExe(conf: ConfigRef; compiler: TSystemCC): string = result = if CC[compiler].linkerExe.len > 0: CC[compiler].linkerExe elif optMixedMode in conf.globalOptions and conf.cmd != cmdCompileToCpp: CC[compiler].cppCompiler - else: getCompilerExe(conf, compiler, "") + else: getCompilerExe(conf, compiler, AbsoluteFile"") proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile): string = var c = conf.cCompiler @@ -565,43 +567,42 @@ proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile): string = includeCmd = "" compilePattern = getCompilerExe(conf, c, cfile.cname) - var cf = if noAbsolutePaths(conf): extractFilename(cfile.cname) + var cf = if noAbsolutePaths(conf): AbsoluteFile extractFilename(cfile.cname.string) else: cfile.cname var objfile = - if cfile.obj.len == 0: + if cfile.obj.isEmpty: if not cfile.flags.contains(CfileFlag.External) or noAbsolutePaths(conf): - toObjFile(conf, cf) + toObjFile(conf, cf).string else: - completeCFilePath(conf, toObjFile(conf, cf)) + completeCFilePath(conf, toObjFile(conf, cf)).string elif noAbsolutePaths(conf): - extractFilename(cfile.obj) + extractFilename(cfile.obj.string) else: - cfile.obj + cfile.obj.string # D files are required by nintendo switch libs for # compilation. They are basically a list of all includes. let dfile = objfile.changeFileExt(".d").quoteShell() objfile = quoteShell(objfile) - cf = quoteShell(cf) + let cfsh = quoteShell(cf) result = quoteShell(compilePattern % [ "dfile", dfile, - "file", cf, "objfile", objfile, "options", options, - "include", includeCmd, "nim", getPrefixDir(conf), - "nim", getPrefixDir(conf), "lib", conf.libpath]) + "file", cfsh, "objfile", objfile, "options", options, + "include", includeCmd, "nim", getPrefixDir(conf).string, + "lib", conf.libpath.string]) add(result, ' ') addf(result, CC[c].compileTmpl, [ "dfile", dfile, - "file", cf, "objfile", objfile, + "file", cfsh, "objfile", objfile, "options", options, "include", includeCmd, "nim", quoteShell(getPrefixDir(conf)), - "nim", quoteShell(getPrefixDir(conf)), "lib", quoteShell(conf.libpath)]) proc footprint(conf: ConfigRef; cfile: Cfile): SecureHash = result = secureHash( - $secureHashFile(cfile.cname) & + $secureHashFile(cfile.cname.string) & platform.OS[conf.target.targetOS].name & platform.CPU[conf.target.targetCPU].name & extccomp.CC[conf.cCompiler].name & @@ -614,14 +615,14 @@ proc externalFileChanged(conf: ConfigRef; cfile: Cfile): bool = var hashFile = toGeneratedFile(conf, conf.withPackageName(cfile.cname), "sha1") var currentHash = footprint(conf, cfile) var f: File - if open(f, hashFile, fmRead): + if open(f, hashFile.string, fmRead): let oldHash = parseSecureHash(f.readLine()) close(f) result = oldHash != currentHash else: result = true if result: - if open(f, hashFile, fmWrite): + if open(f, hashFile.string, fmWrite): f.writeLine($currentHash) close(f) @@ -630,7 +631,7 @@ proc addExternalFileToCompile*(conf: ConfigRef; c: var Cfile) = c.flags.incl CfileFlag.Cached conf.toCompile.add(c) -proc addExternalFileToCompile*(conf: ConfigRef; filename: string) = +proc addExternalFileToCompile*(conf: ConfigRef; filename: AbsoluteFile) = var c = Cfile(cname: filename, obj: toObjFile(conf, completeCFilePath(conf, filename, false)), flags: {CfileFlag.External}) @@ -650,11 +651,11 @@ proc compileCFile(conf: ConfigRef; list: CFileList, script: var Rope, cmds: var add(script, compileCmd) add(script, "\n") -proc getLinkCmd(conf: ConfigRef; projectfile, objfiles: string): string = +proc getLinkCmd(conf: ConfigRef; projectfile: AbsoluteFile, objfiles: string): string = if optGenStaticLib in conf.globalOptions: var libname: string - if conf.outFile.len > 0: - libname = conf.outFile.expandTilde + if not conf.outFile.isEmpty: + libname = conf.outFile.string.expandTilde if not libname.isAbsolute(): libname = getCurrentDir() / libname else: @@ -679,13 +680,13 @@ proc getLinkCmd(conf: ConfigRef; projectfile, objfiles: string): string = else: exefile = splitFile(projectfile).name & platform.OS[conf.target.targetOS].exeExt builddll = "" - if conf.outFile.len > 0: - exefile = conf.outFile.expandTilde + if not conf.outFile.isEmpty: + exefile = conf.outFile.string.expandTilde if not exefile.isAbsolute(): exefile = getCurrentDir() / exefile if not noAbsolutePaths(conf): if not exefile.isAbsolute(): - exefile = joinPath(splitFile(projectfile).dir, exefile) + exefile = string(splitFile(projectfile).dir / RelativeFile(exefile)) when false: if optCDebug in conf.globalOptions: writeDebugInfo(exefile.changeFileExt("ndb")) @@ -693,7 +694,7 @@ proc getLinkCmd(conf: ConfigRef; projectfile, objfiles: string): string = # Map files are required by Nintendo Switch compilation. They are a list # of all function calls in the library and where they come from. - let mapfile = quoteShell(getNimcacheDir(conf) / splitFile(projectFile).name & ".map") + let mapfile = quoteShell(getNimcacheDir(conf) / RelativeFile(splitFile(projectFile).name & ".map")) let linkOptions = getLinkOptions(conf) & " " & getConfigVar(conf, conf.cCompiler, ".options.linker") @@ -703,7 +704,7 @@ proc getLinkCmd(conf: ConfigRef; projectfile, objfiles: string): string = result = quoteShell(result % ["builddll", builddll, "mapfile", mapfile, "buildgui", buildgui, "options", linkOptions, "objfiles", objfiles, - "exefile", exefile, "nim", getPrefixDir(conf), "lib", conf.libpath]) + "exefile", exefile, "nim", getPrefixDir(conf).string, "lib", conf.libpath.string]) result.add ' ' addf(result, linkTmpl, ["builddll", builddll, "mapfile", mapfile, @@ -761,7 +762,7 @@ proc execCmdsInParallel(conf: ConfigRef; cmds: seq[string]; prettyCb: proc (idx: rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" % cmds.join()) -proc callCCompiler*(conf: ConfigRef; projectfile: string) = +proc callCCompiler*(conf: ConfigRef; projectfile: AbsoluteFile) = var linkCmd: string if conf.globalOptions * {optCompileOnly, optGenScript} == {optCompileOnly}: @@ -787,7 +788,7 @@ proc callCCompiler*(conf: ConfigRef; projectfile: string) = add(objfiles, quoteShell( addFileExt(objFile, CC[conf.cCompiler].objExt))) for x in conf.toCompile: - let objFile = if noAbsolutePaths(conf): x.obj.extractFilename else: x.obj + let objFile = if noAbsolutePaths(conf): x.obj.extractFilename else: x.obj.string add(objfiles, ' ') add(objfiles, quoteShell(objFile)) @@ -804,7 +805,7 @@ proc callCCompiler*(conf: ConfigRef; projectfile: string) = #from json import escapeJson import json -proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: string) = +proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile) = template lit(x: untyped) = f.write x template str(x: untyped) = when compiles(escapeJson(x, buf)): @@ -821,7 +822,7 @@ proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: string) = let compileCmd = getCompileCFileCmd(conf, it) if pastStart: lit "],\L" lit "[" - str it.cname + str it.cname.string lit ", " str compileCmd pastStart = true @@ -851,11 +852,10 @@ proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: string) = var buf = newStringOfCap(50) - let file = projectfile.splitFile.name - let jsonFile = toGeneratedFile(conf, file, "json") + let jsonFile = toGeneratedFile(conf, projectfile, "json") var f: File - if open(f, jsonFile, fmWrite): + if open(f, jsonFile.string, fmWrite): lit "{\"compile\":[\L" cfiles(conf, f, buf, conf.toCompile, false) lit "],\L\"link\":[\L" @@ -868,11 +868,10 @@ proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: string) = lit "\L}\L" close(f) -proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: string) = - let file = projectfile.splitFile.name - let jsonFile = toGeneratedFile(conf, file, "json") +proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile) = + let jsonFile = toGeneratedFile(conf, projectfile, "json") try: - let data = json.parseFile(jsonFile) + let data = json.parseFile(jsonFile.string) let toCompile = data["compile"] doAssert toCompile.kind == JArray var cmds: TStringSeq = @[] @@ -896,11 +895,11 @@ proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: string) = except: when declared(echo): echo getCurrentException().getStackTrace() - quit "error evaluating JSON file: " & jsonFile + quit "error evaluating JSON file: " & jsonFile.string proc genMappingFiles(conf: ConfigRef; list: CFileList): Rope = for it in list: - addf(result, "--file:r\"$1\"$N", [rope(it.cname)]) + addf(result, "--file:r\"$1\"$N", [rope(it.cname.string)]) proc writeMapping*(conf: ConfigRef; symbolMapping: Rope) = if optGenMapping notin conf.globalOptions: return @@ -914,9 +913,9 @@ proc writeMapping*(conf: ConfigRef; symbolMapping: Rope) = getConfigVar(conf, conf.cCompiler, ".options.linker"))) add(code, "\n[Environment]\nlibpath=") - add(code, strutils.escape(conf.libpath)) + add(code, strutils.escape(conf.libpath.string)) addf(code, "\n[Symbols]$n$1", [symbolMapping]) - let filename = joinPath(conf.projectPath, "mapping.txt") + let filename = conf.projectPath / RelativeFile"mapping.txt" if not writeRope(code, filename): - rawMessage(conf, errGenerated, "could not write to file: " & filename) + rawMessage(conf, errGenerated, "could not write to file: " & filename.string) diff --git a/compiler/filter_tmpl.nim b/compiler/filter_tmpl.nim index 09455ced7..b884b1ec3 100644 --- a/compiler/filter_tmpl.nim +++ b/compiler/filter_tmpl.nim @@ -11,7 +11,7 @@ import llstream, os, wordrecg, idents, strutils, ast, astalgo, msgs, options, - renderer, filters, lineinfos + renderer, filters, lineinfos, pathutils type TParseState = enum @@ -199,7 +199,8 @@ proc parseLine(p: var TTmplParser) = inc(j) llStreamWrite(p.outp, "\\n\"") -proc filterTmpl*(stdin: PLLStream, filename: string, call: PNode; conf: ConfigRef): PLLStream = +proc filterTmpl*(stdin: PLLStream, filename: AbsoluteFile, + call: PNode; conf: ConfigRef): PLLStream = var p: TTmplParser p.config = conf p.info = newLineInfo(conf, filename, 0, 0) diff --git a/compiler/filters.nim b/compiler/filters.nim index 3ebbad678..d9e8e41da 100644 --- a/compiler/filters.nim +++ b/compiler/filters.nim @@ -11,7 +11,7 @@ import llstream, os, wordrecg, idents, strutils, ast, astalgo, msgs, options, - renderer + renderer, pathutils proc invalidPragma(conf: ConfigRef; n: PNode) = localError(conf, n.info, @@ -47,7 +47,7 @@ proc boolArg*(conf: ConfigRef; n: PNode, name: string, pos: int, default: bool): elif x.kind == nkIdent and cmpIgnoreStyle(x.ident.s, "false") == 0: result = false else: invalidPragma(conf, n) -proc filterStrip*(conf: ConfigRef; stdin: PLLStream, filename: string, call: PNode): PLLStream = +proc filterStrip*(conf: ConfigRef; stdin: PLLStream, filename: AbsoluteFile, call: PNode): PLLStream = var pattern = strArg(conf, call, "startswith", 1, "") var leading = boolArg(conf, call, "leading", 2, true) var trailing = boolArg(conf, call, "trailing", 3, true) @@ -61,7 +61,7 @@ proc filterStrip*(conf: ConfigRef; stdin: PLLStream, filename: string, call: PNo llStreamWriteln(result, line) llStreamClose(stdin) -proc filterReplace*(conf: ConfigRef; stdin: PLLStream, filename: string, call: PNode): PLLStream = +proc filterReplace*(conf: ConfigRef; stdin: PLLStream, filename: AbsoluteFile, call: PNode): PLLStream = var sub = strArg(conf, call, "sub", 1, "") if len(sub) == 0: invalidPragma(conf, call) var by = strArg(conf, call, "by", 2, "") diff --git a/compiler/gorgeimpl.nim b/compiler/gorgeimpl.nim index 44ad46136..44636f382 100644 --- a/compiler/gorgeimpl.nim +++ b/compiler/gorgeimpl.nim @@ -10,7 +10,7 @@ ## Module that implements ``gorge`` for the compiler. import msgs, std / sha1, os, osproc, streams, strutils, options, - lineinfos + lineinfos, pathutils proc readOutput(p: Process): (string, int) = result[0] = "" @@ -26,7 +26,7 @@ proc opGorge*(cmd, input, cache: string, info: TLineInfo; conf: ConfigRef): (str let workingDir = parentDir(toFullPath(conf, info)) if cache.len > 0:# and optForceFullMake notin gGlobalOptions: let h = secureHash(cmd & "\t" & input & "\t" & cache) - let filename = options.toGeneratedFile(conf, "gorge_" & $h, "txt") + let filename = toGeneratedFile(conf, AbsoluteFile("gorge_" & $h), "txt").string var f: File if open(f, filename): result = (f.readAll, 0) diff --git a/compiler/idgen.nim b/compiler/idgen.nim index 7d103ffd7..239df0c57 100644 --- a/compiler/idgen.nim +++ b/compiler/idgen.nim @@ -9,7 +9,7 @@ ## This module contains a simple persistent id generator. -import idents, strutils, os, options +import idents, strutils, os, options, pathutils var gFrontEndId*: int @@ -36,18 +36,18 @@ proc setId*(id: int) {.inline.} = proc idSynchronizationPoint*(idRange: int) = gFrontEndId = (gFrontEndId div idRange + 1) * idRange + 1 -proc toGid(conf: ConfigRef; f: string): string = +proc toGid(conf: ConfigRef; f: AbsoluteFile): string = # we used to use ``f.addFileExt("gid")`` (aka ``$project.gid``), but this # will cause strange bugs if multiple projects are in the same folder, so # we simply use a project independent name: - result = options.completeGeneratedFilePath(conf, "nim.gid") + result = options.completeGeneratedFilePath(conf, AbsoluteFile"nim.gid").string -proc saveMaxIds*(conf: ConfigRef; project: string) = +proc saveMaxIds*(conf: ConfigRef; project: AbsoluteFile) = var f = open(toGid(conf, project), fmWrite) f.writeLine($gFrontEndId) f.close() -proc loadMaxIds*(conf: ConfigRef; project: string) = +proc loadMaxIds*(conf: ConfigRef; project: AbsoluteFile) = var f: File if open(f, toGid(conf, project), fmRead): var line = newStringOfCap(20) diff --git a/compiler/importer.nim b/compiler/importer.nim index 73d2e6599..60b7872fe 100644 --- a/compiler/importer.nim +++ b/compiler/importer.nim @@ -7,15 +7,12 @@ # distribution, for details about the copyright. # -# This module implements the symbol importing mechanism. +## This module implements the symbol importing mechanism. import intsets, strutils, os, ast, astalgo, msgs, options, idents, lookups, semdata, passes, renderer, modulepaths, sigmatch, lineinfos -proc evalImport*(c: PContext, n: PNode): PNode -proc evalFrom*(c: PContext, n: PNode): PNode - proc readExceptSet*(c: PContext, n: PNode): IntSet = assert n.kind in {nkImportExceptStmt, nkExportExceptStmt} result = initIntSet() @@ -140,7 +137,7 @@ proc importModuleAs(c: PContext; n: PNode, realModule: PSym): PSym = c.config.options) proc myImportModule(c: PContext, n: PNode; importStmtResult: PNode): PSym = - var f = checkModuleName(c.config, n) + let f = checkModuleName(c.config, n) if f != InvalidFileIDX: let L = c.graph.importStack.len let recursion = c.graph.importStack.find(f) @@ -168,7 +165,8 @@ proc myImportModule(c: PContext, n: PNode; importStmtResult: PNode): PSym = else: message(c.config, n.info, warnDeprecated, result.name.s) suggestSym(c.config, n.info, result, c.graph.usageSym, false) - importStmtResult.add newStrNode(toFullPath(c.config, f), n.info) + importStmtResult.add newSymNode(result, n.info) + #newStrNode(toFullPath(c.config, f), n.info) proc transformImportAs(c: PContext; n: PNode): PNode = if n.kind == nkInfix and considerQuotedIdent(c, n[0]).s == "as": @@ -188,7 +186,7 @@ proc impMod(c: PContext; it: PNode; importStmtResult: PNode) = importAllSymbolsExcept(c, m, emptySet) #importForwarded(c, m.ast, emptySet) -proc evalImport(c: PContext, n: PNode): PNode = +proc evalImport*(c: PContext, n: PNode): PNode = result = newNodeI(nkImportStmt, n.info) for i in countup(0, sonsLen(n) - 1): let it = n.sons[i] @@ -212,7 +210,7 @@ proc evalImport(c: PContext, n: PNode): PNode = else: impMod(c, it, result) -proc evalFrom(c: PContext, n: PNode): PNode = +proc evalFrom*(c: PContext, n: PNode): PNode = result = newNodeI(nkImportStmt, n.info) checkMinSonsLen(n, 2, c.config) n.sons[0] = transformImportAs(c, n.sons[0]) diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 0fc21b2d1..be3b02388 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -32,7 +32,7 @@ import ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, options, nversion, nimsets, msgs, std / sha1, bitsets, idents, types, os, tables, times, ropes, math, passes, ccgutils, wordrecg, renderer, - intsets, cgmeth, lowerings, sighashes, lineinfos, rodutils + intsets, cgmeth, lowerings, sighashes, lineinfos, rodutils, pathutils from modulegraphs import ModuleGraph @@ -2265,7 +2265,7 @@ proc genClass(conf: ConfigRef; obj: PType; content: Rope; ext: string) = "class $#$# {$n$#$n}$n") % [rope(VersionAsString), cls, extends, content] - let outfile = changeFileExt(completeCFilePath(conf, $cls), ext) + let outfile = changeFileExt(completeCFilePath(conf, AbsoluteFile $cls), ext) discard writeRopeIfNotEqual(result, outfile) proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = @@ -2279,11 +2279,11 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = else: "nimsystem" let code = wholeCode(graph, m) let outfile = - if m.config.outFile.len > 0: - if m.config.outFile.isAbsolute: m.config.outFile - else: getCurrentDir() / m.config.outFile + if not m.config.outFile.isEmpty: + if m.config.outFile.string.isAbsolute: m.config.outFile + else: AbsoluteFile(getCurrentDir() / m.config.outFile.string) else: - changeFileExt(completeCFilePath(m.config, f), ext) + changeFileExt(completeCFilePath(m.config, AbsoluteFile f), ext) discard writeRopeIfNotEqual(genHeader() & code, outfile) for obj, content in items(globals.classes): genClass(m.config, obj, content, ext) diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 278fa1e54..4cb800017 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -17,7 +17,7 @@ import hashes, options, msgs, strutils, platform, idents, nimlexbase, llstream, - wordrecg, lineinfos + wordrecg, lineinfos, pathutils const MaxLineLength* = 80 # lines longer than this lead to a warning @@ -232,7 +232,7 @@ proc openLexer*(lex: var TLexer, fileIdx: FileIndex, inputstream: PLLStream; lex.previousToken.fileIndex = fileIdx lex.config = config -proc openLexer*(lex: var TLexer, filename: string, inputstream: PLLStream; +proc openLexer*(lex: var TLexer, filename: AbsoluteFile, inputstream: PLLStream; cache: IdentCache; config: ConfigRef) = openLexer(lex, fileInfoIdx(config, filename), inputstream, cache, config) diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index 41f3806d4..8749e764d 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -10,7 +10,7 @@ ## This module contains the ``TMsgKind`` enum as well as the ## ``TLineInfo`` object. -import ropes, tables +import ropes, tables, pathutils const explanationsBaseUrl* = "https://nim-lang.org/docs/manual" @@ -179,8 +179,8 @@ const type TFileInfo* = object - fullPath*: string # This is a canonical full filesystem path - projPath*: string # This is relative to the project's root + fullPath*: AbsoluteFile # This is a canonical full filesystem path + projPath*: RelativeFile # This is relative to the project's root shortName*: string # short name of the module quotedName*: Rope # cached quoted short name for codegen # purposes @@ -191,7 +191,7 @@ type # used for better error messages and # embedding the original source in the # generated code - dirtyfile*: string # the file that is actually read into memory + dirtyfile*: AbsoluteFile # the file that is actually read into memory # and parsed; usually "" but is used # for 'nimsuggest' hash*: string # the checksum of the file diff --git a/compiler/linter.nim b/compiler/linter.nim index 7c9cdec83..0b69db8cb 100644 --- a/compiler/linter.nim +++ b/compiler/linter.nim @@ -14,7 +14,7 @@ import strutils, os, intsets, strtabs import options, ast, astalgo, msgs, semdata, ropes, idents, - lineinfos + lineinfos, pathutils const Letters* = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '_'} @@ -42,7 +42,7 @@ proc overwriteFiles*(conf: ConfigRef) = let newFile = if gOverWrite: conf.m.fileInfos[i].fullpath else: conf.m.fileInfos[i].fullpath.changeFileExt(".pretty.nim") try: - var f = open(newFile, fmWrite) + var f = open(newFile.string, fmWrite) for line in conf.m.fileInfos[i].lines: if doStrip: f.write line.strip(leading = false, trailing = true) @@ -51,7 +51,7 @@ proc overwriteFiles*(conf: ConfigRef) = f.write(conf.m.fileInfos[i], "\L") f.close except IOError: - rawMessage(conf, errGenerated, "cannot open file: " & newFile) + rawMessage(conf, errGenerated, "cannot open file: " & newFile.string) proc `=~`(s: string, a: openArray[string]): bool = for x in a: diff --git a/compiler/llstream.nim b/compiler/llstream.nim index 42bbb7600..e121901b1 100644 --- a/compiler/llstream.nim +++ b/compiler/llstream.nim @@ -10,7 +10,7 @@ ## Low-level streams for high performance. import - strutils + strutils, pathutils # support '-d:useGnuReadline' for backwards compatibility: when not defined(windows) and (defined(useGnuReadline) or defined(useLinenoise)): @@ -41,10 +41,10 @@ proc llStreamOpen*(f: File): PLLStream = result.f = f result.kind = llsFile -proc llStreamOpen*(filename: string, mode: FileMode): PLLStream = +proc llStreamOpen*(filename: AbsoluteFile, mode: FileMode): PLLStream = new(result) result.kind = llsFile - if not open(result.f, filename, mode): result = nil + if not open(result.f, filename.string, mode): result = nil proc llStreamOpen*(): PLLStream = new(result) diff --git a/compiler/main.nim b/compiler/main.nim index cd05ded62..6c8b0343e 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -19,7 +19,7 @@ import cgen, jsgen, json, nversion, platform, nimconf, importer, passaux, depends, vm, vmdef, types, idgen, docgen2, parser, modules, ccgutils, sigmatch, ropes, - modulegraphs, tables, rod, lineinfos + modulegraphs, tables, rod, lineinfos, pathutils from magicsys import resetSysTypes @@ -30,8 +30,8 @@ proc semanticPasses(g: ModuleGraph) = registerPass g, verbosePass registerPass g, semPass -proc writeDepsFile(g: ModuleGraph; project: string) = - let f = open(changeFileExt(project, "deps"), fmWrite) +proc writeDepsFile(g: ModuleGraph; project: AbsoluteFile) = + let f = open(changeFileExt(project, "deps").string, fmWrite) for m in g.modules: if m != nil: f.writeLine(toFullPath(g.config, m.position.FileIndex)) @@ -47,8 +47,9 @@ proc commandGenDepend(graph: ModuleGraph) = let project = graph.config.projectFull writeDepsFile(graph, project) generateDot(graph, project) - execExternalProgram(graph.config, "dot -Tpng -o" & changeFileExt(project, "png") & - ' ' & changeFileExt(project, "dot")) + execExternalProgram(graph.config, "dot -Tpng -o" & + changeFileExt(project, "png").string & + ' ' & changeFileExt(project, "dot").string) proc commandCheck(graph: ModuleGraph) = graph.config.errorMax = high(int) # do not stop after first error @@ -126,7 +127,7 @@ proc commandEval(graph: ModuleGraph; exp: string) = makeStdinModule(graph)) proc commandScan(cache: IdentCache, config: ConfigRef) = - var f = addFileExt(mainCommandArg(config), NimExt) + var f = addFileExt(AbsoluteFile mainCommandArg(config), NimExt) var stream = llStreamOpen(f, fmRead) if stream != nil: var @@ -140,7 +141,7 @@ proc commandScan(cache: IdentCache, config: ConfigRef) = if tok.tokType == tkEof: break closeLexer(L) else: - rawMessage(config, errGenerated, "cannot open file: " & f) + rawMessage(config, errGenerated, "cannot open file: " & f.string) const PrintRopeCacheStats = false @@ -231,11 +232,11 @@ proc mainCommand*(graph: ModuleGraph) = for s in definedSymbolNames(conf.symbols): definedSymbols.elems.add(%s) var libpaths = newJArray() - for dir in conf.searchPaths: libpaths.elems.add(%dir) + for dir in conf.searchPaths: libpaths.elems.add(%dir.string) var dumpdata = % [ (key: "version", val: %VersionAsString), - (key: "project_path", val: %conf.projectFull), + (key: "project_path", val: %conf.projectFull.string), (key: "defined_symbols", val: definedSymbols), (key: "lib_paths", val: libpaths) ] @@ -247,7 +248,7 @@ proc mainCommand*(graph: ModuleGraph) = for s in definedSymbolNames(conf.symbols): msgWriteln(conf, s, {msgStdout, msgSkipHook}) msgWriteln(conf, "-- end of list --", {msgStdout, msgSkipHook}) - for it in conf.searchPaths: msgWriteln(conf, it) + for it in conf.searchPaths: msgWriteln(conf, it.string) of "check": conf.cmd = cmdCheck commandCheck(graph) diff --git a/compiler/modulepaths.nim b/compiler/modulepaths.nim index 118002fcf..f0718c4eb 100644 --- a/compiler/modulepaths.nim +++ b/compiler/modulepaths.nim @@ -7,9 +7,8 @@ # distribution, for details about the copyright. # -import ast, renderer, strutils, msgs, options, idents, os, lineinfos - -import nimblecmd +import ast, renderer, strutils, msgs, options, idents, os, lineinfos, + pathutils, nimblecmd when false: const @@ -160,7 +159,7 @@ proc checkModuleName*(conf: ConfigRef; n: PNode; doLocalError=true): FileIndex = # This returns the full canonical path for a given module import let modulename = getModuleName(conf, n) let fullPath = findModule(conf, modulename, toFullPath(conf, n.info)) - if fullPath.len == 0: + if fullPath.isEmpty: if doLocalError: let m = if modulename.len > 0: modulename else: $n localError(conf, n.info, "cannot open file: " & m) diff --git a/compiler/modules.nim b/compiler/modules.nim index b3a1e90d6..8fedba10a 100644 --- a/compiler/modules.nim +++ b/compiler/modules.nim @@ -12,7 +12,7 @@ import ast, astalgo, magicsys, std / sha1, msgs, cgendata, sigmatch, options, idents, os, lexer, idgen, passes, syntaxes, llstream, modulegraphs, rod, - lineinfos + lineinfos, pathutils proc resetSystemArtifacts*(g: ModuleGraph) = magicsys.resetSysTypes(g) @@ -102,19 +102,21 @@ proc connectCallbacks*(graph: ModuleGraph) = proc compileSystemModule*(graph: ModuleGraph) = if graph.systemModule == nil: connectCallbacks(graph) - graph.config.m.systemFileIdx = fileInfoIdx(graph.config, graph.config.libpath / "system.nim") + graph.config.m.systemFileIdx = fileInfoIdx(graph.config, + graph.config.libpath / RelativeFile"system.nim") discard graph.compileModule(graph.config.m.systemFileIdx, {sfSystemModule}) proc wantMainModule*(conf: ConfigRef) = - if conf.projectFull.len == 0: - fatal(conf, newLineInfo(conf, "command line", 1, 1), errGenerated, "command expects a filename") + if conf.projectFull.isEmpty: + fatal(conf, newLineInfo(conf, AbsoluteFile"command line", 1, 1), errGenerated, + "command expects a filename") conf.projectMainIdx = fileInfoIdx(conf, addFileExt(conf.projectFull, NimExt)) proc compileProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIDX) = connectCallbacks(graph) let conf = graph.config wantMainModule(conf) - let systemFileIdx = fileInfoIdx(conf, conf.libpath / "system.nim") + let systemFileIdx = fileInfoIdx(conf, conf.libpath / RelativeFile"system.nim") let projectFile = if projectFileIdx == InvalidFileIDX: conf.projectMainIdx else: projectFileIdx graph.importStack.add projectFile if projectFile == systemFileIdx: @@ -123,8 +125,11 @@ proc compileProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIDX) = graph.compileSystemModule() discard graph.compileModule(projectFile, {sfMainModule}) -proc makeModule*(graph: ModuleGraph; filename: string): PSym = +proc makeModule*(graph: ModuleGraph; filename: AbsoluteFile): PSym = result = graph.newModule(fileInfoIdx(graph.config, filename)) result.id = getID() -proc makeStdinModule*(graph: ModuleGraph): PSym = graph.makeModule"stdin" +proc makeModule*(graph: ModuleGraph; filename: string): PSym = + result = makeModule(graph, AbsoluteFile filename) + +proc makeStdinModule*(graph: ModuleGraph): PSym = graph.makeModule(AbsoluteFile"stdin") diff --git a/compiler/msgs.nim b/compiler/msgs.nim index b7b7c8474..47858f143 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -9,7 +9,7 @@ import options, strutils, os, tables, ropes, platform, terminal, macros, - lineinfos + lineinfos, pathutils proc toCChar*(c: char; result: var string) = case c @@ -35,20 +35,20 @@ proc makeCString*(s: string): Rope = add(result, rope(res)) -proc newFileInfo(fullPath, projPath: string): TFileInfo = +proc newFileInfo(fullPath: AbsoluteFile, projPath: RelativeFile): TFileInfo = result.fullPath = fullPath #shallow(result.fullPath) result.projPath = projPath #shallow(result.projPath) - let fileName = projPath.extractFilename + let fileName = fullPath.extractFilename result.shortName = fileName.changeFileExt("") result.quotedName = fileName.makeCString - result.quotedFullName = fullPath.makeCString + result.quotedFullName = fullPath.string.makeCString result.lines = @[] when defined(nimpretty): - if result.fullPath.len > 0: + if not result.fullPath.isEmpty: try: - result.fullContent = readFile(result.fullPath) + result.fullContent = readFile(result.fullPath.string) except IOError: #rawMessage(errCannotOpenFile, result.fullPath) # XXX fixme @@ -58,39 +58,39 @@ when defined(nimpretty): proc fileSection*(conf: ConfigRef; fid: FileIndex; a, b: int): string = substr(conf.m.fileInfos[fid.int].fullContent, a, b) -proc fileInfoKnown*(conf: ConfigRef; filename: string): bool = +proc fileInfoKnown*(conf: ConfigRef; filename: AbsoluteFile): bool = var - canon: string + canon: AbsoluteFile try: canon = canonicalizePath(conf, filename) except: canon = filename - result = conf.m.filenameToIndexTbl.hasKey(canon) + result = conf.m.filenameToIndexTbl.hasKey(canon.string) -proc fileInfoIdx*(conf: ConfigRef; filename: string; isKnownFile: var bool): FileIndex = +proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile; isKnownFile: var bool): FileIndex = var - canon: string + canon: AbsoluteFile pseudoPath = false try: canon = canonicalizePath(conf, filename) - shallow(canon) + shallow(canon.string) except: canon = filename # The compiler uses "filenames" such as `command line` or `stdin` # This flag indicates that we are working with such a path here pseudoPath = true - if conf.m.filenameToIndexTbl.hasKey(canon): - result = conf.m.filenameToIndexTbl[canon] + if conf.m.filenameToIndexTbl.hasKey(canon.string): + result = conf.m.filenameToIndexTbl[canon.string] else: isKnownFile = false result = conf.m.fileInfos.len.FileIndex - conf.m.fileInfos.add(newFileInfo(canon, if pseudoPath: filename - else: shortenDir(conf, canon))) - conf.m.filenameToIndexTbl[canon] = result + conf.m.fileInfos.add(newFileInfo(canon, if pseudoPath: RelativeFile filename + else: relativeTo(canon, conf.projectPath))) + conf.m.filenameToIndexTbl[canon.string] = result -proc fileInfoIdx*(conf: ConfigRef; filename: string): FileIndex = +proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile): FileIndex = var dummy: bool result = fileInfoIdx(conf, filename, dummy) @@ -99,7 +99,7 @@ proc newLineInfo*(fileInfoIdx: FileIndex, line, col: int): TLineInfo = result.line = uint16(line) result.col = int16(col) -proc newLineInfo*(conf: ConfigRef; filename: string, line, col: int): TLineInfo {.inline.} = +proc newLineInfo*(conf: ConfigRef; filename: AbsoluteFile, line, col: int): TLineInfo {.inline.} = result = newLineInfo(fileInfoIdx(conf, filename), line, col) @@ -152,13 +152,16 @@ proc getInfoContext*(conf: ConfigRef; index: int): TLineInfo = else: result = conf.m.msgContext[i] template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string = - (if fileIdx.int32 < 0 or conf == nil: "???" else: conf.m.fileInfos[fileIdx.int32].projPath) + if fileIdx.int32 < 0 or conf == nil: + "???" + else: + conf.m.fileInfos[fileIdx.int32].projPath.string proc toFullPath*(conf: ConfigRef; fileIdx: FileIndex): string = if fileIdx.int32 < 0 or conf == nil: result = "???" - else: result = conf.m.fileInfos[fileIdx.int32].fullPath + else: result = conf.m.fileInfos[fileIdx.int32].fullPath.string -proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: string) = +proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: AbsoluteFile) = assert fileIdx.int32 >= 0 conf.m.fileInfos[fileIdx.int32].dirtyFile = filename @@ -170,10 +173,10 @@ proc getHash*(conf: ConfigRef; fileIdx: FileIndex): string = assert fileIdx.int32 >= 0 shallowCopy(result, conf.m.fileInfos[fileIdx.int32].hash) -proc toFullPathConsiderDirty*(conf: ConfigRef; fileIdx: FileIndex): string = +proc toFullPathConsiderDirty*(conf: ConfigRef; fileIdx: FileIndex): AbsoluteFile = if fileIdx.int32 < 0: - result = "???" - elif conf.m.fileInfos[fileIdx.int32].dirtyFile.len > 0: + result = AbsoluteFile"???" + elif not conf.m.fileInfos[fileIdx.int32].dirtyFile.isEmpty: result = conf.m.fileInfos[fileIdx.int32].dirtyFile else: result = conf.m.fileInfos[fileIdx.int32].fullPath @@ -188,9 +191,9 @@ proc toMsgFilename*(conf: ConfigRef; info: TLineInfo): string = if info.fileIndex.int32 < 0: result = "???" elif optListFullPaths in conf.globalOptions: - result = conf.m.fileInfos[info.fileIndex.int32].fullPath + result = conf.m.fileInfos[info.fileIndex.int32].fullPath.string else: - result = conf.m.fileInfos[info.fileIndex.int32].projPath + result = conf.m.fileInfos[info.fileIndex.int32].projPath.string proc toLinenumber*(info: TLineInfo): int {.inline.} = result = int info.line diff --git a/compiler/ndi.nim b/compiler/ndi.nim index 9708c388d..f672b1b76 100644 --- a/compiler/ndi.nim +++ b/compiler/ndi.nim @@ -10,7 +10,7 @@ ## This module implements the generation of ``.ndi`` files for better debugging ## support of Nim code. "ndi" stands for "Nim debug info". -import ast, msgs, ropes, options +import ast, msgs, ropes, options, pathutils type NdiFile* = object @@ -30,10 +30,10 @@ proc doWrite(f: var NdiFile; s: PSym; conf: ConfigRef) = template writeMangledName*(f: NdiFile; s: PSym; conf: ConfigRef) = if f.enabled: doWrite(f, s, conf) -proc open*(f: var NdiFile; filename: string; conf: ConfigRef) = - f.enabled = filename.len > 0 +proc open*(f: var NdiFile; filename: AbsoluteFile; conf: ConfigRef) = + f.enabled = not filename.isEmpty if f.enabled: - f.f = open(filename, fmWrite, 8000) + f.f = open(filename.string, fmWrite, 8000) f.buf = newStringOfCap(20) proc close*(f: var NdiFile) = diff --git a/compiler/nim.nim b/compiler/nim.nim index 0fed72dc7..bab1949b5 100644 --- a/compiler/nim.nim +++ b/compiler/nim.nim @@ -21,7 +21,8 @@ when defined(i386) and defined(windows) and defined(vcc): import commands, lexer, condsyms, options, msgs, nversion, nimconf, ropes, extccomp, strutils, os, osproc, platform, main, parseopt, - nodejs, scriptconfig, idents, modulegraphs, lineinfos, cmdlinehelper + nodejs, scriptconfig, idents, modulegraphs, lineinfos, cmdlinehelper, + pathutils when hasTinyCBackend: import tccgen @@ -30,12 +31,12 @@ when defined(profiler) or defined(memProfiler): {.hint: "Profiling support is turned on!".} import nimprof -proc prependCurDir(f: string): string = +proc prependCurDir(f: AbsoluteFile): AbsoluteFile = when defined(unix): - if os.isAbsolute(f): result = f - else: result = "./" & f + if os.isAbsolute(f.string): result = f + else: result = AbsoluteFile("./" & f.string) else: - result = f + result = AbsoluteFile f proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) = var p = parseopt.initOptParser(cmd) @@ -78,15 +79,15 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) = if optRun in conf.globalOptions: if conf.cmd == cmdCompileToJS: var ex: string - if conf.outFile.len > 0: + if not conf.outFile.isEmpty: ex = conf.outFile.prependCurDir.quoteShell else: ex = quoteShell( completeCFilePath(conf, changeFileExt(conf.projectFull, "js").prependCurDir)) execExternalProgram(conf, findNodeJs() & " " & ex & ' ' & conf.arguments) else: - var binPath: string - if conf.outFile.len > 0: + var binPath: AbsoluteFile + if not conf.outFile.isEmpty: # If the user specified an outFile path, use that directly. binPath = conf.outFile.prependCurDir else: diff --git a/compiler/nimblecmd.nim b/compiler/nimblecmd.nim index c5521735b..fa938556b 100644 --- a/compiler/nimblecmd.nim +++ b/compiler/nimblecmd.nim @@ -10,9 +10,9 @@ ## Implements some helper procs for Nimble (Nim's package manager) support. import parseutils, strutils, strtabs, os, options, msgs, sequtils, - lineinfos + lineinfos, pathutils -proc addPath*(conf: ConfigRef; path: string, info: TLineInfo) = +proc addPath*(conf: ConfigRef; path: AbsoluteDir, info: TLineInfo) = if not conf.searchPaths.contains(path): conf.searchPaths.insert(path, 0) @@ -112,9 +112,9 @@ proc addNimblePath(conf: ConfigRef; p: string, info: TLineInfo) = if not path.isAbsolute(): path = p / path - if not contains(conf.searchPaths, path): + if not contains(conf.searchPaths, AbsoluteDir path): message(conf, info, hintPath, path) - conf.lazyPaths.insert(path, 0) + conf.lazyPaths.insert(AbsoluteDir path, 0) proc addPathRec(conf: ConfigRef; dir: string, info: TLineInfo) = var packages = newStringTable(modeStyleInsensitive) @@ -126,9 +126,9 @@ proc addPathRec(conf: ConfigRef; dir: string, info: TLineInfo) = for p in packages.chosen: addNimblePath(conf, p, info) -proc nimblePath*(conf: ConfigRef; path: string, info: TLineInfo) = - addPathRec(conf, path, info) - addNimblePath(conf, path, info) +proc nimblePath*(conf: ConfigRef; path: AbsoluteDir, info: TLineInfo) = + addPathRec(conf, path.string, info) + addNimblePath(conf, path.string, info) when isMainModule: proc v(s: string): Version = s.newVersion diff --git a/compiler/nimconf.nim b/compiler/nimconf.nim index 5f6889a6f..c0aeab7e3 100644 --- a/compiler/nimconf.nim +++ b/compiler/nimconf.nim @@ -11,7 +11,7 @@ import llstream, nversion, commands, os, strutils, msgs, platform, condsyms, lexer, - options, idents, wordrecg, strtabs, lineinfos + options, idents, wordrecg, strtabs, lineinfos, pathutils # ---------------- configuration file parser ----------------------------- # we use Nim's scanner here to save space and work @@ -201,8 +201,8 @@ proc parseAssignment(L: var TLexer, tok: var TToken; else: processSwitch(s, val, passPP, info, config) -proc readConfigFile( - filename: string; cache: IdentCache; config: ConfigRef): bool = +proc readConfigFile(filename: AbsoluteFile; cache: IdentCache; + config: ConfigRef): bool = var L: TLexer tok: TToken @@ -219,24 +219,24 @@ proc readConfigFile( closeLexer(L) return true -proc getUserConfigPath*(filename: string): string = - result = joinPath([getConfigDir(), "nim", filename]) +proc getUserConfigPath*(filename: RelativeFile): AbsoluteFile = + result = getConfigDir().AbsoluteDir / RelativeDir"nim" / filename -proc getSystemConfigPath*(conf: ConfigRef; filename: string): string = +proc getSystemConfigPath*(conf: ConfigRef; filename: RelativeFile): AbsoluteFile = # try standard configuration file (installation did not distribute files # the UNIX way) let p = getPrefixDir(conf) - result = joinPath([p, "config", filename]) + result = p / RelativeDir"config" / filename when defined(unix): - if not existsFile(result): result = joinPath([p, "etc/nim", filename]) - if not existsFile(result): result = "/etc/nim/" & filename + if not fileExists(result): result = p / RelativeDir"etc/nim" / filename + if not fileExists(result): result = AbsoluteDir"/etc/nim" / filename -proc loadConfigs*(cfg: string; cache: IdentCache; conf: ConfigRef) = +proc loadConfigs*(cfg: RelativeFile; cache: IdentCache; conf: ConfigRef) = setDefaultLibpath(conf) - var configFiles = newSeq[string]() + var configFiles = newSeq[AbsoluteFile]() - template readConfigFile(path: string) = + template readConfigFile(path) = let configPath = path if readConfigFile(configPath, cache, conf): add(configFiles, configPath) @@ -247,10 +247,10 @@ proc loadConfigs*(cfg: string; cache: IdentCache; conf: ConfigRef) = if optSkipUserConfigFile notin conf.globalOptions: readConfigFile(getUserConfigPath(cfg)) - let pd = if conf.projectPath.len > 0: conf.projectPath else: getCurrentDir() + let pd = if not conf.projectPath.isEmpty: conf.projectPath else: AbsoluteDir(getCurrentDir()) if optSkipParentConfigFiles notin conf.globalOptions: - for dir in parentDirs(pd, fromRoot=true, inclusive=false): - readConfigFile(dir / cfg) + for dir in parentDirs(pd.string, fromRoot=true, inclusive=false): + readConfigFile(AbsoluteDir(dir) / cfg) if optSkipProjConfigFile notin conf.globalOptions: readConfigFile(pd / cfg) @@ -264,4 +264,4 @@ proc loadConfigs*(cfg: string; cache: IdentCache; conf: ConfigRef) = for filename in configFiles: # delayed to here so that `hintConf` is honored - rawMessage(conf, hintConf, filename) + rawMessage(conf, hintConf, filename.string) diff --git a/compiler/nimeval.nim b/compiler/nimeval.nim index f20b5642c..841c38a46 100644 --- a/compiler/nimeval.nim +++ b/compiler/nimeval.nim @@ -11,7 +11,7 @@ import ast, astalgo, modules, passes, condsyms, options, sem, semdata, llstream, vm, vmdef, - modulegraphs, idents, os + modulegraphs, idents, os, pathutils type Interpreter* = ref object ## Use Nim as an interpreter with this object @@ -103,8 +103,8 @@ proc createInterpreter*(scriptName: string; registerPass(graph, evalPass) for p in searchPaths: - conf.searchPaths.add(p) - if conf.libpath.len == 0: conf.libpath = p + conf.searchPaths.add(AbsoluteDir p) + if conf.libpath.isEmpty: conf.libpath = AbsoluteDir p var m = graph.makeModule(scriptName) incl(m.flags, sfMainModule) diff --git a/compiler/nversion.nim b/compiler/nversion.nim index 4b8cf7100..8981ae213 100644 --- a/compiler/nversion.nim +++ b/compiler/nversion.nim @@ -15,6 +15,6 @@ const VersionAsString* = system.NimVersion RodFileVersion* = "1223" # modify this if the rod-format changes! - NimCompilerApiVersion* = 2 ## Check for the existance of this before accessing it + NimCompilerApiVersion* = 3 ## Check for the existance of this before accessing it ## as older versions of the compiler API do not ## declare this. diff --git a/compiler/options.nim b/compiler/options.nim index c9334991a..4927579e3 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -9,7 +9,7 @@ import os, strutils, strtabs, osproc, sets, lineinfos, platform, - prefixmatches + prefixmatches, pathutils from terminal import isatty from times import utc, fromUnix, local, getTime, format, DateTime @@ -135,7 +135,7 @@ type External ## file was introduced via .compile pragma Cfile* = object - cname*, obj*: string + cname*, obj*: AbsoluteFile flags*: set[CFileFlag] CfileList* = seq[Cfile] @@ -203,13 +203,14 @@ type ## symbols are always guaranteed to be style ## insensitive. Otherwise hell would break lose. packageCache*: StringTableRef - searchPaths*: seq[string] - lazyPaths*: seq[string] - outFile*, prefixDir*, libpath*, nimcacheDir*: string + searchPaths*: seq[AbsoluteDir] + lazyPaths*: seq[AbsoluteDir] + outFile*: AbsoluteFile + prefixDir*, libpath*, nimcacheDir*: AbsoluteDir dllOverrides, moduleOverrides*: StringTableRef projectName*: string # holds a name like 'nim' - projectPath*: string # holds a path like /home/alice/projects/nim/compiler/ - projectFull*: string # projectPath/projectName + projectPath*: AbsoluteDir # holds a path like /home/alice/projects/nim/compiler/ + projectFull*: AbsoluteFile # projectPath/projectName projectIsStdin*: bool # whether we're compiling from stdin projectMainIdx*: FileIndex # the canonical path id of the main module command*: string # the main command (e.g. cc, check, scan, etc) @@ -221,9 +222,9 @@ type # The string uses the formatting variables `path` and `line`. # the used compiler - cIncludes*: seq[string] # directories to search for included files - cLibs*: seq[string] # directories to search for lib files - cLinkedLibs*: seq[string] # libraries to link + cIncludes*: seq[AbsoluteDir] # directories to search for included files + cLibs*: seq[AbsoluteDir] # directories to search for lib files + cLinkedLibs*: seq[string] # libraries to link externalToLink*: seq[string] # files to link in addition to the file # we compiled (*) @@ -302,12 +303,13 @@ proc newConfigRef*(): ConfigRef = packageCache: newPackageCache(), searchPaths: @[], lazyPaths: @[], - outFile: "", prefixDir: "", libpath: "", nimcacheDir: "", + outFile: AbsoluteFile"", prefixDir: AbsoluteDir"", + libpath: AbsoluteDir"", nimcacheDir: AbsoluteDir"", dllOverrides: newStringTable(modeCaseInsensitive), moduleOverrides: newStringTable(modeStyleInsensitive), projectName: "", # holds a name like 'nim' - projectPath: "", # holds a path like /home/alice/projects/nim/compiler/ - projectFull: "", # projectPath/projectName + projectPath: AbsoluteDir"", # holds a path like /home/alice/projects/nim/compiler/ + projectFull: AbsoluteFile"", # projectPath/projectName projectIsStdin: false, # whether we're compiling from stdin projectMainIdx: FileIndex(0'i32), # the canonical path id of the main module command: "", # the main command (e.g. cc, check, scan, etc) @@ -401,7 +403,7 @@ template optPreserveOrigSource*(conf: ConfigRef): untyped = optEmbedOrigSrc in conf.globalOptions const - genSubDir* = "nimcache" + genSubDir* = RelativeDir"nimcache" NimExt* = "nim" RodExt* = "rod" HtmlExt* = "html" @@ -409,10 +411,10 @@ const TagsExt* = "tags" TexExt* = "tex" IniExt* = "ini" - DefaultConfig* = "nim.cfg" - DefaultConfigNims* = "config.nims" - DocConfig* = "nimdoc.cfg" - DocTexConfig* = "nimdoc.tex.cfg" + DefaultConfig* = RelativeFile"nim.cfg" + DefaultConfigNims* = RelativeFile"config.nims" + DocConfig* = RelativeFile"nimdoc.cfg" + DocTexConfig* = RelativeFile"nimdoc.tex.cfg" const oKeepVariableNames* = true @@ -437,56 +439,61 @@ proc getConfigVar*(conf: ConfigRef; key: string, default = ""): string = proc setConfigVar*(conf: ConfigRef; key, val: string) = conf.configVars[key] = val -proc getOutFile*(conf: ConfigRef; filename, ext: string): string = - if conf.outFile != "": result = conf.outFile - else: result = changeFileExt(filename, ext) +proc getOutFile*(conf: ConfigRef; filename: RelativeFile, ext: string): AbsoluteFile = + if not conf.outFile.isEmpty: result = conf.outFile + else: result = conf.projectPath / changeFileExt(filename, ext) -proc getPrefixDir*(conf: ConfigRef): string = +proc getPrefixDir*(conf: ConfigRef): AbsoluteDir = ## Gets the prefix dir, usually the parent directory where the binary resides. ## ## This is overridden by some tools (namely nimsuggest) via the ``conf.prefixDir`` - ## global. - if conf.prefixDir != "": result = conf.prefixDir - else: result = splitPath(getAppDir()).head + ## field. + if not conf.prefixDir.isEmpty: result = conf.prefixDir + else: result = AbsoluteDir splitPath(getAppDir()).head proc setDefaultLibpath*(conf: ConfigRef) = # set default value (can be overwritten): - if conf.libpath == "": + if conf.libpath.isEmpty: # choose default libpath: var prefix = getPrefixDir(conf) when defined(posix): - if prefix == "/usr": conf.libpath = "/usr/lib/nim" - elif prefix == "/usr/local": conf.libpath = "/usr/local/lib/nim" - else: conf.libpath = joinPath(prefix, "lib") - else: conf.libpath = joinPath(prefix, "lib") + if prefix == AbsoluteDir"/usr": + conf.libpath = AbsoluteDir"/usr/lib/nim" + elif prefix == AbsoluteDir"/usr/local": + conf.libpath = AbsoluteDir"/usr/local/lib/nim" + else: + conf.libpath = prefix / RelativeDir"lib" + else: + conf.libpath = prefix / RelativeDir"lib" # Special rule to support other tools (nimble) which import the compiler # modules and make use of them. let realNimPath = findExe("nim") # Find out if $nim/../../lib/system.nim exists. let parentNimLibPath = realNimPath.parentDir.parentDir / "lib" - if not fileExists(conf.libpath / "system.nim") and + if not fileExists(conf.libpath.string / "system.nim") and fileExists(parentNimlibPath / "system.nim"): - conf.libpath = parentNimLibPath + conf.libpath = AbsoluteDir parentNimLibPath -proc canonicalizePath*(conf: ConfigRef; path: string): string = +proc canonicalizePath*(conf: ConfigRef; path: AbsoluteFile): AbsoluteFile = # on Windows, 'expandFilename' calls getFullPathName which doesn't do # case corrections, so we have to use this convoluted way of retrieving # the true filename (see tests/modules and Nimble uses 'import Uri' instead # of 'import uri'): when defined(windows): - result = path.expandFilename - for x in walkFiles(result): - return x + result = AbsoluteFile path.string.expandFilename + for x in walkFiles(result.string): + return AbsoluteFile x else: - result = path.expandFilename + result = AbsoluteFile path.string.expandFilename -proc shortenDir*(conf: ConfigRef; dir: string): string = +proc shortenDir*(conf: ConfigRef; dir: string): string {. + deprecated: "use 'relativeTo' instead".} = ## returns the interesting part of a dir - var prefix = conf.projectPath & DirSep + var prefix = conf.projectPath.string & DirSep if startsWith(dir, prefix): return substr(dir, len(prefix)) - prefix = getPrefixDir(conf) & DirSep + prefix = getPrefixDir(conf).string & DirSep if startsWith(dir, prefix): return substr(dir, len(prefix)) result = dir @@ -509,87 +516,87 @@ proc getOsCacheDir(): string = else: result = getHomeDir() / genSubDir -proc getNimcacheDir*(conf: ConfigRef): string = +proc getNimcacheDir*(conf: ConfigRef): AbsoluteDir = # XXX projectName should always be without a file extension! - result = if conf.nimcacheDir.len > 0: + result = if not conf.nimcacheDir.isEmpty: conf.nimcacheDir elif conf.cmd == cmdCompileToJS: - shortenDir(conf, conf.projectPath) / genSubDir - else: getOsCacheDir() / splitFile(conf.projectName).name & - (if isDefined(conf, "release"): "_r" else: "_d") + conf.projectPath / genSubDir + else: + AbsoluteDir(getOsCacheDir() / splitFile(conf.projectName).name & + (if isDefined(conf, "release"): "_r" else: "_d")) proc pathSubs*(conf: ConfigRef; p, config: string): string = let home = removeTrailingDirSep(os.getHomeDir()) result = unixToNativePath(p % [ - "nim", getPrefixDir(conf), - "lib", conf.libpath, + "nim", getPrefixDir(conf).string, + "lib", conf.libpath.string, "home", home, "config", config, "projectname", conf.projectName, - "projectpath", conf.projectPath, - "projectdir", conf.projectPath, - "nimcache", getNimcacheDir(conf)]) + "projectpath", conf.projectPath.string, + "projectdir", conf.projectPath.string, + "nimcache", getNimcacheDir(conf).string]) if "~/" in result: result = result.replace("~/", home & '/') -proc toGeneratedFile*(conf: ConfigRef; path, ext: string): string = +proc toGeneratedFile*(conf: ConfigRef; path: AbsoluteFile, + ext: string): AbsoluteFile = ## converts "/home/a/mymodule.nim", "rod" to "/home/a/nimcache/mymodule.rod" - var (head, tail) = splitPath(path) - #if len(head) > 0: head = shortenDir(head & dirSep) - result = joinPath([getNimcacheDir(conf), changeFileExt(tail, ext)]) - #echo "toGeneratedFile(", path, ", ", ext, ") = ", result - -proc completeGeneratedFilePath*(conf: ConfigRef; f: string, createSubDir: bool = true): string = - var (head, tail) = splitPath(f) - #if len(head) > 0: head = removeTrailingDirSep(shortenDir(head & dirSep)) - var subdir = getNimcacheDir(conf) # / head + let (head, tail) = splitPath(path.string) + result = getNimcacheDir(conf) / RelativeFile changeFileExt(tail, ext) + +proc completeGeneratedFilePath*(conf: ConfigRef; f: AbsoluteFile, + createSubDir: bool = true): AbsoluteFile = + let (head, tail) = splitPath(f.string) + let subdir = getNimcacheDir(conf) if createSubDir: try: - createDir(subdir) + createDir(subdir.string) except OSError: - writeLine(stdout, "cannot create directory: " & subdir) + writeLine(stdout, "cannot create directory: " & subdir.string) quit(1) - result = joinPath(subdir, tail) + result = subdir / RelativeFile tail #echo "completeGeneratedFilePath(", f, ") = ", result -proc rawFindFile(conf: ConfigRef; f: string; suppressStdlib: bool): string = +proc rawFindFile(conf: ConfigRef; f: RelativeFile; suppressStdlib: bool): AbsoluteFile = for it in conf.searchPaths: - if suppressStdlib and it.startsWith(conf.libpath): + if suppressStdlib and it.string.startsWith(conf.libpath.string): continue - result = joinPath(it, f) - if existsFile(result): + result = it / f + if fileExists(result): return canonicalizePath(conf, result) - result = "" + result = AbsoluteFile"" -proc rawFindFile2(conf: ConfigRef; f: string): string = +proc rawFindFile2(conf: ConfigRef; f: RelativeFile): AbsoluteFile = for i, it in conf.lazyPaths: - result = joinPath(it, f) - if existsFile(result): + result = it / f + if fileExists(result): # bring to front for j in countDown(i,1): swap(conf.lazyPaths[j], conf.lazyPaths[j-1]) return canonicalizePath(conf, result) - result = "" + result = AbsoluteFile"" template patchModule(conf: ConfigRef) {.dirty.} = - if result.len > 0 and conf.moduleOverrides.len > 0: - let key = getPackageName(conf, result) & "_" & splitFile(result).name + if not result.isEmpty and conf.moduleOverrides.len > 0: + let key = getPackageName(conf, result.string) & "_" & splitFile(result).name if conf.moduleOverrides.hasKey(key): let ov = conf.moduleOverrides[key] - if ov.len > 0: result = ov + if ov.len > 0: result = AbsoluteFile(ov) -proc findFile*(conf: ConfigRef; f: string; suppressStdlib = false): string {.procvar.} = +proc findFile*(conf: ConfigRef; f: string; suppressStdlib = false): AbsoluteFile {.procvar.} = if f.isAbsolute: - result = if f.existsFile: f else: "" + result = if f.existsFile: AbsoluteFile(f) else: AbsoluteFile"" else: - result = rawFindFile(conf, f, suppressStdlib) - if result.len == 0: - result = rawFindFile(conf, f.toLowerAscii, suppressStdlib) - if result.len == 0: - result = rawFindFile2(conf, f) - if result.len == 0: - result = rawFindFile2(conf, f.toLowerAscii) + result = rawFindFile(conf, RelativeFile f, suppressStdlib) + if result.isEmpty: + result = rawFindFile(conf, RelativeFile f.toLowerAscii, suppressStdlib) + if result.isEmpty: + result = rawFindFile2(conf, RelativeFile f) + if result.isEmpty: + result = rawFindFile2(conf, RelativeFile f.toLowerAscii) patchModule(conf) const stdlibDirs = [ @@ -599,7 +606,7 @@ const stdlibDirs = [ "wrappers", "wrappers/linenoise", "windows", "posix", "js"] -proc findModule*(conf: ConfigRef; modulename, currentModule: string): string = +proc findModule*(conf: ConfigRef; modulename, currentModule: string): AbsoluteFile = # returns path to module const pkgPrefix = "pkg/" const stdPrefix = "std/" @@ -610,13 +617,13 @@ proc findModule*(conf: ConfigRef; modulename, currentModule: string): string = if m.startsWith(stdPrefix): let stripped = m.substr(stdPrefix.len) for candidate in stdlibDirs: - let path = (conf.libpath / candidate / stripped) + let path = (conf.libpath.string / candidate / stripped) if fileExists(path): m = path break let currentPath = currentModule.splitFile.dir - result = currentPath / m - if not existsFile(result): + result = AbsoluteFile currentPath / m + if not fileExists(result): result = findFile(conf, m) patchModule(conf) diff --git a/compiler/packagehandling.nim b/compiler/packagehandling.nim index 7414aeb71..f94c3d72c 100644 --- a/compiler/packagehandling.nim +++ b/compiler/packagehandling.nim @@ -41,10 +41,10 @@ proc getPackageName*(conf: ConfigRef; path: string): string = dec parents if parents <= 0: break -proc withPackageName*(conf: ConfigRef; path: string): string = - let x = getPackageName(conf, path) +proc withPackageName*(conf: ConfigRef; path: AbsoluteFile): AbsoluteFile = + let x = getPackageName(conf, path.string) if x.len == 0: result = path else: let (p, file, ext) = path.splitFile - result = (p / (x & '_' & file)) & ext + result = p / RelativeFile((x & '_' & file) & ext) diff --git a/compiler/parser.nim b/compiler/parser.nim index f15449c85..245d2e390 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -27,7 +27,8 @@ when isMainModule: outp.close import - llstream, lexer, idents, strutils, ast, astalgo, msgs, options, lineinfos + llstream, lexer, idents, strutils, ast, astalgo, msgs, options, lineinfos, + pathutils when defined(nimpretty2): import layouter @@ -114,7 +115,7 @@ proc openParser*(p: var TParser, fileIdx: FileIndex, inputStream: PLLStream, p.strongSpaces = strongSpaces p.emptyNode = newNode(nkEmpty) -proc openParser*(p: var TParser, filename: string, inputStream: PLLStream, +proc openParser*(p: var TParser, filename: AbsoluteFile, inputStream: PLLStream, cache: IdentCache; config: ConfigRef; strongSpaces=false) = openParser(p, fileInfoIdx(config, filename), inputStream, cache, config, strongSpaces) @@ -2235,7 +2236,7 @@ proc parseString*(s: string; cache: IdentCache; config: ConfigRef; # XXX for now the builtin 'parseStmt/Expr' functions do not know about strong # spaces... parser.lex.errorHandler = errorHandler - openParser(parser, filename, stream, cache, config, false) + openParser(parser, AbsoluteFile filename, stream, cache, config, false) result = parser.parseAll closeParser(parser) diff --git a/compiler/passes.nim b/compiler/passes.nim index 45c726f2a..365731669 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -14,7 +14,7 @@ import strutils, options, ast, astalgo, llstream, msgs, platform, os, condsyms, idents, renderer, types, extccomp, math, magicsys, nversion, nimsets, syntaxes, times, idgen, modulegraphs, reorder, rod, - lineinfos + lineinfos, pathutils type @@ -106,7 +106,7 @@ proc processTopLevelStmt(n: PNode, a: var TPassContextArray): bool = proc resolveMod(conf: ConfigRef; module, relativeTo: string): FileIndex = let fullPath = findModule(conf, module, relativeTo) - if fullPath.len == 0: + if fullPath.isEmpty: result = InvalidFileIDX else: result = fileInfoIdx(conf, fullPath) @@ -160,7 +160,7 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool { let filename = toFullPathConsiderDirty(graph.config, fileIdx) s = llStreamOpen(filename, fmRead) if s == nil: - rawMessage(graph.config, errCannotOpenFile, filename) + rawMessage(graph.config, errCannotOpenFile, filename.string) return false else: s = stream diff --git a/compiler/pathutils.nim b/compiler/pathutils.nim new file mode 100644 index 000000000..f84d964bb --- /dev/null +++ b/compiler/pathutils.nim @@ -0,0 +1,254 @@ +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Path handling utilities for Nim. Strictly typed code in order +## to avoid the never ending time sink in getting path handling right. +## Might be a candidate for the stdlib later. + +import os, strutils + +type + AbsoluteFile* = distinct string + AbsoluteDir* = distinct string + RelativeFile* = distinct string + RelativeDir* = distinct string + +proc isEmpty*(x: AbsoluteFile): bool {.inline.} = x.string.len == 0 +proc isEmpty*(x: AbsoluteDir): bool {.inline.} = x.string.len == 0 +proc isEmpty*(x: RelativeFile): bool {.inline.} = x.string.len == 0 +proc isEmpty*(x: RelativeDir): bool {.inline.} = x.string.len == 0 + +proc copyFile*(source, dest: AbsoluteFile) = + os.copyFile(source.string, dest.string) + +proc removeFile*(x: AbsoluteFile) {.borrow.} + +proc splitFile*(x: AbsoluteFile): tuple[dir: AbsoluteDir, name, ext: string] = + let (a, b, c) = splitFile(x.string) + result = (dir: AbsoluteDir(a), name: b, ext: c) + +proc extractFilename*(x: AbsoluteFile): string {.borrow.} + +proc fileExists*(x: AbsoluteFile): bool {.borrow.} +proc dirExists*(x: AbsoluteDir): bool {.borrow.} + +proc quoteShell*(x: AbsoluteFile): string {.borrow.} +proc quoteShell*(x: AbsoluteDir): string {.borrow.} + +proc cmpPaths*(x, y: AbsoluteDir): int {.borrow.} + +proc createDir*(x: AbsoluteDir) {.borrow.} + +type + PathIter = object + i, prev: int + notFirst: bool + +proc hasNext(it: PathIter; x: string): bool = + it.i < x.len + +proc next(it: var PathIter; x: string): (int, int) = + it.prev = it.i + if not it.notFirst and x[it.i] in {DirSep, AltSep}: + # absolute path: + inc it.i + else: + while it.i < x.len and x[it.i] notin {DirSep, AltSep}: inc it.i + if it.i > it.prev: + result = (it.prev, it.i-1) + elif hasNext(it, x): + result = next(it, x) + + # skip all separators: + while it.i < x.len and x[it.i] in {DirSep, AltSep}: inc it.i + it.notFirst = true + +iterator dirs(x: string): (int, int) = + var it: PathIter + while hasNext(it, x): yield next(it, x) + +when false: + iterator dirs(x: string): (int, int) = + var i = 0 + var first = true + while i < x.len: + let prev = i + if first and x[i] in {DirSep, AltSep}: + # absolute path: + inc i + else: + while i < x.len and x[i] notin {DirSep, AltSep}: inc i + if i > prev: + yield (prev, i-1) + first = false + # skip all separators: + while i < x.len and x[i] in {DirSep, AltSep}: inc i + +proc isDot(x: string; bounds: (int, int)): bool = + bounds[1] == bounds[0] and x[bounds[0]] == '.' + +proc isDotDot(x: string; bounds: (int, int)): bool = + bounds[1] == bounds[0] + 1 and x[bounds[0]] == '.' and x[bounds[0]+1] == '.' + +proc isSlash(x: string; bounds: (int, int)): bool = + bounds[1] == bounds[0] and x[bounds[0]] in {DirSep, AltSep} + +proc canon(x: string; result: var string; state: var int) = + # state: 0th bit set if isAbsolute path. Other bits count + # the number of path components. + for b in dirs(x): + if (state shr 1 == 0) and isSlash(x, b): + result.add DirSep + state = state or 1 + elif result.len > (state and 1) and isDotDot(x, b): + var d = result.len + # f/.. + while d > (state and 1) and result[d-1] != DirSep: + dec d + setLen(result, d) + elif isDot(x, b): + discard "discard the dot" + else: + if result.len > (state and 1): result.add DirSep + result.add substr(x, b[0], b[1]) + inc state, 2 + +proc canon(x: string): string = + # - Turn multiple slashes into single slashes. + # - Resolve '/foo/../bar' to '/bar'. + # - Remove './' from the path. + result = newStringOfCap(x.len) + var state = 0 + canon(x, result, state) + +when FileSystemCaseSensitive: + template `!=?`(a, b: char): bool = toLowerAscii(a) != toLowerAscii(b) +else: + template `!=?`(a, b: char): bool = a != b + +proc relativeTo(full, base: string; sep = DirSep): string = + if full.len == 0: return "" + var f, b: PathIter + var ff = (0, -1) + var bb = (0, -1) # (int, int) + result = newStringOfCap(full.len) + # skip the common prefix: + while f.hasNext(full) and b.hasNext(base): + ff = next(f, full) + bb = next(b, base) + let diff = ff[1] - ff[0] + if diff != bb[1] - bb[0]: break + var same = true + for i in 0..diff: + if full[i + ff[0]] !=? base[i + bb[0]]: + same = false + break + if not same: break + ff = (0, -1) + bb = (0, -1) + # for i in 0..diff: + # result.add base[i + bb[0]] + + # /foo/bar/xxx/ -- base + # /foo/bar/baz -- full path + # ../baz + # every directory that is in 'base', needs to add '..' + while true: + if bb[1] >= bb[0]: + if result.len > 0 and result[^1] != sep: + result.add sep + result.add ".." + if not b.hasNext(base): break + bb = b.next(base) + + # add the rest of 'full': + while true: + if ff[1] >= ff[0]: + if result.len > 0 and result[^1] != sep: + result.add sep + for i in 0..ff[1] - ff[0]: + result.add full[i + ff[0]] + if not f.hasNext(full): break + ff = f.next(full) + +when true: + proc eqImpl(x, y: string): bool = + when FileSystemCaseSensitive: + result = toLowerAscii(canon x) == toLowerAscii(canon y) + else: + result = canon(x) == canon(y) + + proc `==`*(x, y: AbsoluteFile): bool = eqImpl(x.string, y.string) + proc `==`*(x, y: AbsoluteDir): bool = eqImpl(x.string, y.string) + proc `==`*(x, y: RelativeFile): bool = eqImpl(x.string, y.string) + proc `==`*(x, y: RelativeDir): bool = eqImpl(x.string, y.string) + + proc `/`*(base: AbsoluteDir; f: RelativeFile): AbsoluteFile = + assert isAbsolute(base.string) + assert(not isAbsolute(f.string)) + result = AbsoluteFile newStringOfCap(base.string.len + f.string.len) + var state = 0 + canon(base.string, result.string, state) + canon(f.string, result.string, state) + + proc `/`*(base: AbsoluteDir; f: RelativeDir): AbsoluteDir = + assert isAbsolute(base.string) + assert(not isAbsolute(f.string)) + result = AbsoluteDir newStringOfCap(base.string.len + f.string.len) + var state = 0 + canon(base.string, result.string, state) + canon(f.string, result.string, state) + + proc relativeTo*(fullPath: AbsoluteFile, baseFilename: AbsoluteDir; + sep = DirSep): RelativeFile = + RelativeFile(relativeTo(fullPath.string, baseFilename.string, sep)) + + proc toAbsolute*(file: string; base: AbsoluteDir): AbsoluteFile = + if isAbsolute(file): result = AbsoluteFile(file) + else: result = base / RelativeFile file + + proc changeFileExt*(x: AbsoluteFile; ext: string): AbsoluteFile {.borrow.} + proc changeFileExt*(x: RelativeFile; ext: string): RelativeFile {.borrow.} + + proc addFileExt*(x: AbsoluteFile; ext: string): AbsoluteFile {.borrow.} + proc addFileExt*(x: RelativeFile; ext: string): RelativeFile {.borrow.} + + proc writeFile*(x: AbsoluteFile; content: string) {.borrow.} + +when isMainModule and defined(posix): + doAssert canon"/foo/../bar" == "/bar" + doAssert canon"foo/../bar" == "bar" + + doAssert canon"/f/../bar///" == "/bar" + doAssert canon"f/..////bar" == "bar" + + doAssert canon"../bar" == "../bar" + doAssert canon"/../bar" == "/../bar" + + doAssert canon("foo/../../bar/") == "../bar" + doAssert canon("./bla/blob/") == "bla/blob" + doAssert canon(".hiddenFile") == ".hiddenFile" + doAssert canon("./bla/../../blob/./zoo.nim") == "../blob/zoo.nim" + + doAssert canon("C:/file/to/this/long") == "C:/file/to/this/long" + doAssert canon("") == "" + doAssert canon("foobar") == "foobar" + doAssert canon("f/////////") == "f" + + doAssert relativeTo("/foo/bar//baz.nim", "/foo") == "bar/baz.nim" + + doAssert relativeTo("/Users/me/bar/z.nim", "/Users/other/bad") == "../../me/bar/z.nim" + + doAssert relativeTo("/Users/me/bar/z.nim", "/Users/other") == "../me/bar/z.nim" + doAssert relativeTo("/Users///me/bar//z.nim", "//Users/") == "me/bar/z.nim" + doAssert relativeTo("/Users/me/bar/z.nim", "/Users/me") == "bar/z.nim" + doAssert relativeTo("", "/users/moo") == "" + doAssert relativeTo("foo", "") == "foo" + + echo string(AbsoluteDir"/Users/me///" / RelativeFile"z.nim") diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 9a344c038..ad287178e 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -12,7 +12,7 @@ import os, platform, condsyms, ast, astalgo, idents, semdata, msgs, renderer, wordrecg, ropes, options, strutils, extccomp, math, magicsys, trees, - types, lookups, lineinfos + types, lookups, lineinfos, pathutils const FirstCallConv* = wNimcall @@ -442,26 +442,22 @@ proc processUndef(c: PContext, n: PNode) = else: invalidPragma(c, n) -type - TLinkFeature = enum - linkNormal, linkSys - -proc relativeFile(c: PContext; n: PNode; ext=""): string = +proc relativeFile(c: PContext; n: PNode; ext=""): AbsoluteFile = var s = expectStrLit(c, n) if ext.len > 0 and splitFile(s).ext == "": s = addFileExt(s, ext) - result = parentDir(toFullPath(c.config, n.info)) / s + result = AbsoluteFile parentDir(toFullPath(c.config, n.info)) / s if not fileExists(result): - if isAbsolute(s): result = s + if isAbsolute(s): result = AbsoluteFile s else: result = findFile(c.config, s) - if result.len == 0: result = s + if result.isEmpty: result = AbsoluteFile s proc processCompile(c: PContext, n: PNode) = - proc docompile(c: PContext; it: PNode; src, dest: string) = + proc docompile(c: PContext; it: PNode; src, dest: AbsoluteFile) = var cf = Cfile(cname: src, obj: dest, flags: {CfileFlag.External}) extccomp.addExternalFileToCompile(c.config, cf) - recordPragma(c, it, "compile", src, dest) + recordPragma(c, it, "compile", src.string, dest.string) proc getStrLit(c: PContext, n: PNode; i: int): string = n.sons[i] = c.semConstExpr(c, n[i]) @@ -478,30 +474,23 @@ proc processCompile(c: PContext, n: PNode) = let dest = getStrLit(c, it, 1) var found = parentDir(toFullPath(c.config, n.info)) / s for f in os.walkFiles(found): - let obj = completeCFilePath(c.config, dest % extractFilename(f)) - docompile(c, it, f, obj) + let obj = completeCFilePath(c.config, AbsoluteFile(dest % extractFilename(f))) + docompile(c, it, AbsoluteFile f, obj) else: let s = expectStrLit(c, n) - var found = parentDir(toFullPath(c.config, n.info)) / s + var found = AbsoluteFile(parentDir(toFullPath(c.config, n.info)) / s) if not fileExists(found): - if isAbsolute(s): found = s + if isAbsolute(s): found = AbsoluteFile s else: found = findFile(c.config, s) - if found.len == 0: found = s + if found.isEmpty: found = AbsoluteFile s let obj = toObjFile(c.config, completeCFilePath(c.config, found, false)) docompile(c, it, found, obj) -proc processCommonLink(c: PContext, n: PNode, feature: TLinkFeature) = +proc processLink(c: PContext, n: PNode) = let found = relativeFile(c, n, CC[c.config.cCompiler].objExt) - case feature - of linkNormal: - extccomp.addExternalFileToLink(c.config, found) - recordPragma(c, n, "link", found) - of linkSys: - let dest = c.config.libpath / completeCFilePath(c.config, found, false) - extccomp.addExternalFileToLink(c.config, dest) - recordPragma(c, n, "link", dest) - else: internalError(c.config, n.info, "processCommonLink") + extccomp.addExternalFileToLink(c.config, found) + recordPragma(c, n, "link", found.string) proc pragmaBreakpoint(c: PContext, n: PNode) = discard getOptionalStr(c, n, "") @@ -594,8 +583,9 @@ proc pragmaLine(c: PContext, n: PNode) = elif y.kind != nkIntLit: localError(c.config, n.info, errIntLiteralExpected) else: - # XXX this produces weird paths which are not properly resolved: - n.info.fileIndex = msgs.fileInfoIdx(c.config, x.strVal) + # XXX check if paths are properly resolved this way: + let dir = toFullPath(c.config, n.info).splitFile.dir + n.info.fileIndex = fileInfoIdx(c.config, AbsoluteDir(dir) / RelativeFile(x.strVal)) n.info.line = uint16(y.intVal) else: localError(c.config, n.info, "tuple expected") @@ -959,8 +949,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, of wDefine: processDefine(c, it) of wUndef: processUndef(c, it) of wCompile: processCompile(c, it) - of wLink: processCommonLink(c, it, linkNormal) - of wLinksys: processCommonLink(c, it, linkSys) + of wLink: processLink(c, it) of wPassl: let s = expectStrLit(c, it) extccomp.addLinkOption(c.config, s) diff --git a/compiler/renderer.nim b/compiler/renderer.nim index aa666290c..964af0591 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -15,11 +15,12 @@ import type TRenderFlag* = enum renderNone, renderNoBody, renderNoComments, renderDocComments, - renderNoPragmas, renderIds, renderNoProcDefs + renderNoPragmas, renderIds, renderNoProcDefs, renderSyms TRenderFlags* = set[TRenderFlag] TRenderTok* = object kind*: TTokType length*: int16 + sym*: PSym TRenderTokSeq* = seq[TRenderTok] TSrcGen* = object @@ -105,11 +106,12 @@ proc initSrcGen(g: var TSrcGen, renderFlags: TRenderFlags; config: ConfigRef) = g.inGenericParams = false g.config = config -proc addTok(g: var TSrcGen, kind: TTokType, s: string) = +proc addTok(g: var TSrcGen, kind: TTokType, s: string; sym: PSym = nil) = var length = len(g.tokens) setLen(g.tokens, length + 1) g.tokens[length].kind = kind g.tokens[length].length = int16(len(s)) + g.tokens[length].sym = sym add(g.buf, s) proc addPendingNL(g: var TSrcGen) = @@ -165,11 +167,11 @@ proc dedent(g: var TSrcGen) = dec(g.pendingNL, IndentWidth) dec(g.lineLen, IndentWidth) -proc put(g: var TSrcGen, kind: TTokType, s: string) = +proc put(g: var TSrcGen, kind: TTokType, s: string; sym: PSym = nil) = if kind != tkSpaces: addPendingNL(g) if len(s) > 0: - addTok(g, kind, s) + addTok(g, kind, s, sym) inc(g.lineLen, len(s)) else: g.pendingWhitespace = s.len @@ -836,7 +838,7 @@ proc gident(g: var TSrcGen, n: PNode) = t = tkSymbol else: t = tkOpr - put(g, t, s) + put(g, t, s, if n.kind == nkSym and renderSyms in g.flags: n.sym else: nil) if n.kind == nkSym and (renderIds in g.flags or sfGenSym in n.sym.flags): when defined(debugMagics): put(g, tkIntLit, $n.sym.id & $n.sym.magic) @@ -1541,3 +1543,9 @@ proc getNextTok*(r: var TSrcGen, kind: var TTokType, literal: var string) = inc(r.idx) else: kind = tkEof + +proc getTokSym*(r: TSrcGen): PSym = + if r.idx > 0 and r.idx <= len(r.tokens): + result = r.tokens[r.idx-1].sym + else: + result = nil diff --git a/compiler/rodimpl.nim b/compiler/rodimpl.nim index 7d24e4e67..b5891fcfd 100644 --- a/compiler/rodimpl.nim +++ b/compiler/rodimpl.nim @@ -11,7 +11,7 @@ import strutils, os, intsets, tables, ropes, db_sqlite, msgs, options, types, renderer, rodutils, idents, astalgo, btrees, magicsys, cgmeth, extccomp, - btrees, trees, condsyms, nversion + btrees, trees, condsyms, nversion, pathutils ## Todo: ## - Dependency computation should use *signature* hashes in order to @@ -796,7 +796,7 @@ proc replay(g: ModuleGraph; module: PSym; n: PNode) = flags: {CfileFlag.External}) extccomp.addExternalFileToCompile(g.config, cf) of "link": - extccomp.addExternalFileToLink(g.config, n[1].strVal) + extccomp.addExternalFileToLink(g.config, AbsoluteFile n[1].strVal) of "passl": extccomp.addLinkOption(g.config, n[1].strVal) of "passc": diff --git a/compiler/ropes.nim b/compiler/ropes.nim index 81ee01dbf..0d6d7d78f 100644 --- a/compiler/ropes.nim +++ b/compiler/ropes.nim @@ -58,6 +58,8 @@ import hashes +from pathutils import AbsoluteFile + type FormatStr* = string # later we may change it to CString for better # performance of the code generator (assignments @@ -183,9 +185,9 @@ proc writeRope*(f: File, r: Rope) = ## writes a rope to a file. for s in leaves(r): write(f, s) -proc writeRope*(head: Rope, filename: string): bool = +proc writeRope*(head: Rope, filename: AbsoluteFile): bool = var f: File - if open(f, filename, fmWrite): + if open(f, filename.string, fmWrite): if head != nil: writeRope(f, head) close(f) result = true @@ -314,16 +316,16 @@ proc equalsFile*(r: Rope, f: File): bool = result = readBuffer(f, addr(buf[0]), 1) == 0 and btotal == rtotal # check that we've read all -proc equalsFile*(r: Rope, filename: string): bool = +proc equalsFile*(r: Rope, filename: AbsoluteFile): bool = ## returns true if the contents of the file `f` equal `r`. If `f` does not ## exist, false is returned. var f: File - result = open(f, filename) + result = open(f, filename.string) if result: result = equalsFile(r, f) close(f) -proc writeRopeIfNotEqual*(r: Rope, filename: string): bool = +proc writeRopeIfNotEqual*(r: Rope, filename: AbsoluteFile): bool = # returns true if overwritten if not equalsFile(r, filename): result = writeRope(r, filename) diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim index 184b60733..cf69e29f1 100644 --- a/compiler/scriptconfig.nim +++ b/compiler/scriptconfig.nim @@ -13,7 +13,7 @@ import ast, modules, idents, passes, passaux, condsyms, options, nimconf, sem, semdata, llstream, vm, vmdef, commands, msgs, - os, times, osproc, wordrecg, strtabs, modulegraphs, lineinfos + os, times, osproc, wordrecg, strtabs, modulegraphs, lineinfos, pathutils # we support 'cmpIgnoreStyle' natively for efficiency: from strutils import cmpIgnoreStyle, contains @@ -105,7 +105,7 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; cbconf exists: setResult(a, options.existsConfigVar(conf, a.getString 0)) cbconf nimcacheDir: - setResult(a, options.getNimcacheDir(conf)) + setResult(a, options.getNimcacheDir(conf).string) cbconf paramStr: setResult(a, os.paramStr(int a.getInt 0)) cbconf paramCount: @@ -120,8 +120,8 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; if arg.len > 0: conf.projectName = arg let path = - if conf.projectName.isAbsolute: conf.projectName - else: conf.projectPath / conf.projectName + if conf.projectName.isAbsolute: AbsoluteFile(conf.projectName) + else: conf.projectPath / RelativeFile(conf.projectName) try: conf.projectFull = canonicalizePath(conf, path) except OSError: @@ -149,9 +149,9 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; cbconf cppDefine: options.cppDefine(conf, a.getString(0)) -proc runNimScript*(cache: IdentCache; scriptName: string; +proc runNimScript*(cache: IdentCache; scriptName: AbsoluteFile; freshDefines=true; conf: ConfigRef) = - rawMessage(conf, hintConf, scriptName) + rawMessage(conf, hintConf, scriptName.string) let graph = newModuleGraph(cache, conf) connectCallbacks(graph) @@ -169,7 +169,7 @@ proc runNimScript*(cache: IdentCache; scriptName: string; var m = graph.makeModule(scriptName) incl(m.flags, sfMainModule) - graph.vm = setupVM(m, cache, scriptName, graph) + graph.vm = setupVM(m, cache, scriptName.string, graph) graph.compileSystemModule() # TODO: see why this unsets hintConf in conf.notes discard graph.processModule(m, llStreamOpen(scriptName, fmRead)) diff --git a/compiler/suggest.nim b/compiler/suggest.nim index f99a2d432..b6b8d713c 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -32,7 +32,7 @@ # included from sigmatch.nim -import algorithm, prefixmatches, lineinfos +import algorithm, prefixmatches, lineinfos, pathutils from wordrecg import wDeprecated when defined(nimsuggest): @@ -319,7 +319,7 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) if n.kind == nkSym and n.sym.kind == skError and c.config.suggestVersion == 0: # consider 'foo.|' where 'foo' is some not imported module. let fullPath = findModule(c.config, n.sym.name.s, toFullPath(c.config, n.info)) - if fullPath.len == 0: + if fullPath.isEmpty: # error: no known module name: typ = nil else: diff --git a/compiler/syntaxes.nim b/compiler/syntaxes.nim index 069f65eee..b5fcee7b1 100644 --- a/compiler/syntaxes.nim +++ b/compiler/syntaxes.nim @@ -11,7 +11,7 @@ import strutils, llstream, ast, astalgo, idents, lexer, options, msgs, parser, - filters, filter_tmpl, renderer, lineinfos + filters, filter_tmpl, renderer, lineinfos, pathutils type TFilterKind* = enum @@ -58,7 +58,7 @@ proc containsShebang(s: string, i: int): bool = while j < s.len and s[j] in Whitespace: inc(j) result = s[j] == '/' -proc parsePipe(filename: string, inputStream: PLLStream; cache: IdentCache; +proc parsePipe(filename: AbsoluteFile, inputStream: PLLStream; cache: IdentCache; config: ConfigRef): PNode = result = newNode(nkEmpty) var s = llStreamOpen(filename, fmRead) @@ -100,7 +100,7 @@ proc getCallee(conf: ConfigRef; n: PNode): PIdent = else: localError(conf, n.info, "invalid filter: " & renderTree(n)) -proc applyFilter(p: var TParsers, n: PNode, filename: string, +proc applyFilter(p: var TParsers, n: PNode, filename: AbsoluteFile, stdin: PLLStream): PLLStream = var ident = getCallee(p.config, n) var f = getFilter(ident) @@ -121,7 +121,7 @@ proc applyFilter(p: var TParsers, n: PNode, filename: string, msgWriteln(p.config, result.s) rawMessage(p.config, hintCodeEnd, []) -proc evalPipe(p: var TParsers, n: PNode, filename: string, +proc evalPipe(p: var TParsers, n: PNode, filename: AbsoluteFile, start: PLLStream): PLLStream = assert p.config != nil result = start @@ -161,8 +161,8 @@ proc parseFile*(fileIdx: FileIndex; cache: IdentCache; config: ConfigRef): PNode p: TParsers f: File let filename = toFullPathConsiderDirty(config, fileIdx) - if not open(f, filename): - rawMessage(config, errGenerated, "cannot open file: " & filename) + if not open(f, filename.string): + rawMessage(config, errGenerated, "cannot open file: " & filename.string) return openParsers(p, fileIdx, llStreamOpen(f), cache, config) result = parseAll(p) diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index bf2418eaf..eb6111165 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -13,7 +13,7 @@ proc opSlurp*(file: string, info: TLineInfo, module: PSym; conf: ConfigRef): str try: var filename = parentDir(toFullPath(conf, info)) / file if not fileExists(filename): - filename = findFile(conf, file) + filename = findFile(conf, file).string result = readFile(filename) # we produce a fake include statement for every slurped filename, so that # the module dependencies are accurate: diff --git a/compiler/vmhooks.nim b/compiler/vmhooks.nim index 548a3af97..39e435e4b 100644 --- a/compiler/vmhooks.nim +++ b/compiler/vmhooks.nim @@ -7,6 +7,8 @@ # distribution, for details about the copyright. # +import pathutils + template setX(k, field) {.dirty.} = var s: seq[TFullReg] move(s, cast[seq[TFullReg]](a.slots)) @@ -38,6 +40,8 @@ proc setResult*(a: VmArgs; n: PNode) = s[a.ra].kind = rkNode s[a.ra].node = n +proc setResult*(a: VmArgs; v: AbsoluteDir) = setResult(a, v.string) + proc setResult*(a: VmArgs; v: seq[string]) = var s: seq[TFullReg] move(s, cast[seq[TFullReg]](a.slots)) diff --git a/compiler/vmops.nim b/compiler/vmops.nim index a7d47d7a3..83e65279a 100644 --- a/compiler/vmops.nim +++ b/compiler/vmops.nim @@ -82,7 +82,7 @@ proc registerAdditionalOps*(c: PCtx) = setResult a, newTree(nkTupleConstr, newStrNode(nkStrLit, s), newIntNode(nkIntLit, e)) proc getProjectPathWrapper(a: VmArgs) = - setResult a, c.config.projectPath + setResult a, c.config.projectPath.string wrap1f_math(sqrt) wrap1f_math(ln) diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim index b20572b0e..34b1cc4f7 100644 --- a/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -20,7 +20,8 @@ import compiler / [options, commands, modules, sem, passes, passaux, msgs, nimconf, extccomp, condsyms, sigmatch, ast, scriptconfig, - idents, modulegraphs, vm, prefixmatches, lineinfos, cmdlinehelper] + idents, modulegraphs, vm, prefixmatches, lineinfos, cmdlinehelper, + pathutils] when defined(windows): import winlean @@ -158,10 +159,11 @@ proc symFromInfo(graph: ModuleGraph; trackPos: TLineInfo): PSym = if m != nil and m.ast != nil: result = findNode(m.ast, trackPos) -proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int; +proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; graph: ModuleGraph) = let conf = graph.config - myLog("cmd: " & $cmd & ", file: " & file & ", dirtyFile: " & dirtyfile & + myLog("cmd: " & $cmd & ", file: " & file.string & + ", dirtyFile: " & dirtyfile.string & "[" & $line & ":" & $col & "]") conf.ideCmd = cmd if cmd == ideChk: @@ -175,8 +177,8 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int; var isKnownFile = true let dirtyIdx = fileInfoIdx(conf, file, isKnownFile) - if dirtyfile.len != 0: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile) - else: msgs.setDirtyFile(conf, dirtyIdx, "") + if not dirtyfile.isEmpty: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile) + else: msgs.setDirtyFile(conf, dirtyIdx, AbsoluteFile"") conf.m.trackPos = newLineInfo(dirtyIdx, line, col) conf.m.trackPosAttached = false @@ -186,7 +188,7 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int; if not isKnownFile: graph.compileProject() if conf.suggestVersion == 0 and conf.ideCmd in {ideUse, ideDus} and - dirtyfile.len == 0: + dirtyfile.isEmpty: discard "no need to recompile anything" else: let modIdx = graph.parentModule(dirtyIdx) @@ -204,12 +206,12 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int; proc executeEpc(cmd: IdeCmd, args: SexpNode; graph: ModuleGraph) = let - file = args[0].getStr + file = AbsoluteFile args[0].getStr line = args[1].getNum column = args[2].getNum - var dirtyfile = "" + var dirtyfile = AbsoluteFile"" if len(args) > 3: - dirtyfile = args[3].getStr("") + dirtyfile = AbsoluteFile args[3].getStr("") execute(cmd, file, dirtyfile, int(line), int(column), graph) proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string, @@ -431,11 +433,11 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) = i += parseInt(cmd, col, i) if conf.ideCmd == ideKnown: - results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, orig)))) + results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, AbsoluteFile orig)))) else: if conf.ideCmd == ideChk: for cm in cachedMsgs: errorHook(conf, cm.info, cm.msg, cm.sev) - execute(conf.ideCmd, orig, dirtyfile, line, col, graph) + execute(conf.ideCmd, AbsoluteFile orig, AbsoluteFile dirtyfile, line, col, graph) sentinel() proc recompileFullProject(graph: ModuleGraph) = @@ -451,7 +453,7 @@ proc mainThread(graph: ModuleGraph) = let conf = graph.config if gLogging: for it in conf.searchPaths: - log(it) + log(it.string) proc wrHook(line: string) {.closure.} = if gMode == mepc: @@ -496,7 +498,7 @@ proc mainCommand(graph: ModuleGraph) = wantMainModule(conf) if not fileExists(conf.projectFull): - quit "cannot find file: " & conf.projectFull + quit "cannot find file: " & conf.projectFull.string add(conf.searchPaths, conf.libpath) @@ -518,9 +520,9 @@ proc mainCommand(graph: ModuleGraph) = of mtcp: createThread(inputThread, replTcp, (gPort, gAddress)) of mepc: createThread(inputThread, replEpc, (gPort, gAddress)) of mcmdsug: createThread(inputThread, replCmdline, - (gPort, "sug \"" & conf.projectFull & "\":" & gAddress)) + (gPort, "sug \"" & conf.projectFull.string & "\":" & gAddress)) of mcmdcon: createThread(inputThread, replCmdline, - (gPort, "con \"" & conf.projectFull & "\":" & gAddress)) + (gPort, "con \"" & conf.projectFull.string & "\":" & gAddress)) mainThread(graph) joinThread(inputThread) close(requests) @@ -602,11 +604,12 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) = if binaryPath == "": raise newException(IOError, "Cannot find Nim standard library: Nim compiler not in PATH") - conf.prefixDir = binaryPath.splitPath().head.parentDir() - if not dirExists(conf.prefixDir / "lib"): conf.prefixDir = "" + conf.prefixDir = AbsoluteDir binaryPath.splitPath().head.parentDir() + if not dirExists(conf.prefixDir / RelativeDir"lib"): + conf.prefixDir = AbsoluteDir"" #msgs.writelnHook = proc (line: string) = log(line) - myLog("START " & conf.projectFull) + myLog("START " & conf.projectFull.string) discard self.loadConfigsAndRunMainCommand(cache, conf) |