diff options
Diffstat (limited to 'compiler')
55 files changed, 1191 insertions, 705 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/closureiters.nim b/compiler/closureiters.nim index e3e9c2236..b0857e6c7 100644 --- a/compiler/closureiters.nim +++ b/compiler/closureiters.nim @@ -712,6 +712,25 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode = result.add(n) + of nkBracketExpr: + var lhsNeedsSplit = false + var rhsNeedsSplit = false + n[0] = ctx.lowerStmtListExprs(n[0], lhsNeedsSplit) + n[1] = ctx.lowerStmtListExprs(n[1], rhsNeedsSplit) + if lhsNeedsSplit or rhsNeedsSplit: + needsSplit = true + result = newNodeI(nkStmtListExpr, n.info) + if lhsNeedsSplit: + let (st, ex) = exprToStmtList(n[0]) + result.add(st) + n[0] = ex + + if rhsNeedsSplit: + let (st, ex) = exprToStmtList(n[1]) + result.add(st) + n[1] = ex + result.add(n) + of nkWhileStmt: var ns = false 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 23d156e05..ca3b1ac2d 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 + typesrenderer, astalgo, modulepaths, lineinfos, sequtils, intsets, + pathutils type TSections = array[TSymKind, Rope] @@ -32,6 +33,10 @@ type conf*: ConfigRef cache*: IdentCache 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. @@ -46,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) @@ -72,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 = "" @@ -88,13 +93,29 @@ 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; guessTarget: bool): 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) + elif guessTarget: + let d = if not conf.outFile.isEmpty: splitFile(conf.outFile).dir + else: conf.projectPath + 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"): @@ -118,7 +139,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", false) + 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) @@ -224,6 +250,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 = "" @@ -256,8 +286,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", sfMainModule notin s.owner.flags) + + 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: @@ -287,23 +331,25 @@ 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 + " --path:" & quoteShell(d.conf.projectPath) & + " --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) = @@ -329,7 +375,8 @@ proc isRunnableExample(n: PNode): bool = result = n.kind == nkSym and n.sym.magic == mRunnableExamples or n.kind == nkIdent and n.ident.s == "runnableExamples" -proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) = +proc getAllRunnableExamplesRec(d: PDoc; n, orig: PNode; dest: var Rope) = + if n.info.fileIndex != orig.info.fileIndex: return case n.kind of nkCallKinds: if isRunnableExample(n[0]) and @@ -354,7 +401,10 @@ proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) = dest.add(d.config.getOrDefault"doc.listing_end" % id) else: discard for i in 0 ..< n.safeLen: - getAllRunnableExamples(d, n[i], dest) + getAllRunnableExamplesRec(d, n[i], orig, dest) + +proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) = + getAllRunnableExamplesRec(d, n, n, dest) when false: proc findDocComment(n: PNode): PNode = @@ -377,7 +427,7 @@ when false: else: result = n.comment.substr(2).replace("\n##", "\n").strip -proc isVisible(n: PNode): bool = +proc isVisible(d: PDoc; n: PNode): bool = result = false if n.kind == nkPostfix: if n.len == 2 and n.sons[0].kind == nkIdent: @@ -388,8 +438,10 @@ proc isVisible(n: PNode): bool = # exception tracking information here. Instead we copy over the comment # from the proc header. result = {sfExported, sfFromGeneric, sfForward}*n.sym.flags == {sfExported} + if result and containsOrIncl(d.emitted, n.sym.id): + result = false elif n.kind == nkPragmaExpr: - result = isVisible(n.sons[0]) + result = isVisible(d, n.sons[0]) proc getName(d: PDoc, n: PNode, splitAfter = -1): string = case n.kind @@ -444,10 +496,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): @@ -455,7 +505,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. ## @@ -477,11 +526,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 @@ -490,7 +537,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. ## @@ -518,9 +564,8 @@ proc docstringSummary(rstText: string): string = result.delete(pos, last) result.add("…") - proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = - if not isVisible(nameNode): return + if not isVisible(d, nameNode): return let name = getName(d, nameNode) nameRope = name.rope @@ -540,8 +585,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 @@ -558,16 +603,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"), @@ -576,11 +623,22 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = [nameRope, result, comm, itemIDRope, plainNameRope, plainSymbolRope, symbolOrIdRope, plainSymbolEncRope, symbolOrIdEncRope, seeSrcRope])) + let external = AbsoluteFile(d.filename).relativeTo(d.conf.projectPath, '/').changeFileExt(HtmlExt).string + var attype: Rope if k in routineKinds and nameNode.kind == nkSym: let att = attachToType(d, nameNode.sym) if att != nil: attype = rope esc(d.target, att.name.s) + elif k == skType and nameNode.kind == nkSym and nameNode.sym.typ.kind in {tyEnum, tyBool}: + let etyp = nameNode.sym.typ + for e in etyp.n: + if e.sym.kind != skEnumField: continue + let plain = renderPlainSymbolName(e) + let symbolOrId = d.newUniquePlainSymbol(plain) + setIndexTerm(d[], external, symbolOrId, plain, nameNode.sym.name.s & '.' & plain, + xmltree.escape(getPlainDocstring(e).docstringSummary)) + add(d.toc[k], ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.item.toc"), ["name", "header", "desc", "itemID", "header_plain", "itemSym", "itemSymOrID", "itemSymEnc", "itemSymOrIDEnc", "attype"], @@ -591,24 +649,22 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = # Ironically for types the complexSymbol is *cleaner* than the plainName # because it doesn't include object fields or documentation comments. So we # use the plain one for callable elements, and the complex for the rest. - var linkTitle = changeFileExt(extractFilename(d.filename), "") & " : " + var linkTitle = changeFileExt(extractFilename(d.filename), "") & ": " if n.isCallable: linkTitle.add(xmltree.escape(plainName.strip)) else: linkTitle.add(xmltree.escape(complexSymbol.strip)) - setIndexTerm(d[], symbolOrId, name, linkTitle, + setIndexTerm(d[], external, symbolOrId, name, linkTitle, xmltree.escape(plainDocstring.docstringSummary)) if k == skType and nameNode.kind == nkSym: d.types.strTableAdd nameNode.sym proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode = - if not isVisible(nameNode): return + if not isVisible(d, nameNode): return var 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: @@ -621,7 +677,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] @@ -632,13 +687,19 @@ 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", sfMainModule notin it.sym.flags) + let external = relativeTo(tmp, d.thisDir, '/').string 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, changeFileExt(external, "")), + rope changeFileExt(external, "html")]) -proc generateDoc*(d: PDoc, n: PNode) = +proc generateDoc*(d: PDoc, n, orig: PNode) = + if orig.info.fileIndex != n.info.fileIndex: return case n.kind of nkCommentStmt: add(d.modDesc, genComment(d, n)) of nkProcDef: @@ -665,11 +726,11 @@ proc generateDoc*(d: PDoc, n: PNode) = genItem(d, n.sons[i], n.sons[i].sons[0], succ(skType, ord(n.kind)-ord(nkTypeSection))) of nkStmtList: - for i in countup(0, sonsLen(n) - 1): generateDoc(d, n.sons[i]) + for i in countup(0, sonsLen(n) - 1): generateDoc(d, n.sons[i], orig) of nkWhenStmt: # generate documentation for the first branch only: if not checkForFalse(n.sons[0].sons[0]): - generateDoc(d, lastSon(n.sons[0])) + generateDoc(d, lastSon(n.sons[0]), orig) of nkImportStmt: for i in 0 .. sonsLen(n)-1: traceDeps(d, n.sons[i]) of nkFromStmt, nkImportExceptStmt: traceDeps(d, n.sons[0]) @@ -798,7 +859,8 @@ proc genOutFile(d: PDoc): Rope = # Extract the title. Non API modules generate an entry in the index table. if d.meta[metaTitle].len != 0: title = d.meta[metaTitle] - setIndexTerm(d[], "", title) + let external = AbsoluteFile(d.filename).relativeTo(d.conf.projectPath, '/').changeFileExt(HtmlExt).string + setIndexTerm(d[], external, "", title) else: # Modules get an automatic title for the HTML, but no entry in the index. title = "Module " & extractFilename(changeFileExt(d.filename, "")) @@ -824,29 +886,27 @@ 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) - -proc writeOutput*(d: PDoc, filename, outExt: string, useWarning = false) = + let dir = if d.conf.outFile.isEmpty: d.conf.projectPath / RelativeDir"htmldocs" + elif optWholeProject in d.conf.globalOptions: AbsoluteDir(d.conf.outFile) + else: AbsoluteDir(d.conf.outFile.string.splitFile.dir) + createDir(dir) + let dest = dir / changeFileExt(relativeTo(AbsoluteFile d.filename, + d.conf.projectPath), IndexExt) + writeIndexFile(d[], dest.string) + +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} @@ -854,8 +914,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: @@ -866,27 +925,28 @@ proc commandDoc*(cache: IdentCache, conf: ConfigRef) = if ast == nil: return var d = newDocumentor(conf.projectFull, cache, conf) d.hasToc = true - generateDoc(d, ast) - writeOutput(d, conf.projectFull, HtmlExt) + generateDoc(d, ast, ast) + 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) @@ -894,14 +954,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) = @@ -923,9 +981,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) @@ -940,12 +998,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", @@ -953,6 +1011,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..01acdd4ca 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,17 +39,17 @@ 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 var g = PGen(c) if shouldProcess(g): - generateDoc(g.doc, n) + generateDoc(g.doc, n, n) proc processNodeJson(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 1b00ddbfa..aa2386526 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 @@ -658,15 +658,16 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) = line(p, "}\L") proc genRaiseStmt(p: PProc, n: PNode) = - genLineDir(p, n) if n.sons[0].kind != nkEmpty: var a: TCompRes gen(p, n.sons[0], a) let typ = skipTypes(n.sons[0].typ, abstractPtrs) + genLineDir(p, n) useMagic(p, "raiseException") lineF(p, "raiseException($1, $2);$n", [a.rdLoc, makeJSString(typ.sym.name.s)]) else: + genLineDir(p, n) useMagic(p, "reraiseException") line(p, "reraiseException();\L") @@ -764,11 +765,22 @@ proc genAsmOrEmitStmt(p: PProc, n: PNode) = of nkSym: let v = it.sym # for backwards compatibility we don't deref syms here :-( - if v.kind in {skVar, skLet, skTemp, skConst, skResult, skParam, skForVar}: - p.body.add mangleName(p.module, v) + if false: + discard else: var r: TCompRes gen(p, it, r) + + if it.typ.kind == tyPointer: + # A fat pointer is disguised as an array + r.res = r.address + r.address = nil + elif r.typ == etyBaseIndex: + # Deference first + r.res = "$1[$2]" % [r.address, r.res] + r.address = nil + r.typ = etyNone + p.body.add(r.rdLoc) else: var r: TCompRes @@ -845,6 +857,7 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = else: gen(p, x, a) + genLineDir(p, y) gen(p, y, b) # we don't care if it's an etyBaseIndex (global) of a string, it's @@ -881,11 +894,9 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = lineF(p, "$1 = $2;$n", [a.res, b.res]) proc genAsgn(p: PProc, n: PNode) = - genLineDir(p, n) genAsgnAux(p, n.sons[0], n.sons[1], noCopyNeeded=false) proc genFastAsgn(p: PProc, n: PNode) = - genLineDir(p, n) # 'shallowCopy' always produced 'noCopyNeeded = true' here but this is wrong # for code like # while j >= pos: @@ -1529,18 +1540,18 @@ proc genConStrStr(p: PProc, n: PNode, r: var TCompRes) = if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyChar: r.res.add("[$1].concat(" % [a.res]) else: - r.res.add("($1.slice(0,-1)).concat(" % [a.res]) + r.res.add("($1).concat(" % [a.res]) for i in countup(2, sonsLen(n) - 2): gen(p, n.sons[i], a) if skipTypes(n.sons[i].typ, abstractVarRange).kind == tyChar: r.res.add("[$1]," % [a.res]) else: - r.res.add("$1.slice(0,-1)," % [a.res]) + r.res.add("$1," % [a.res]) gen(p, n.sons[sonsLen(n) - 1], a) if skipTypes(n.sons[sonsLen(n) - 1].typ, abstractVarRange).kind == tyChar: - r.res.add("[$1, 0])" % [a.res]) + r.res.add("[$1])" % [a.res]) else: r.res.add("$1)" % [a.res]) @@ -1647,14 +1658,20 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = else: unaryExpr(p, n, r, "subInt", "subInt($1, 1)") of mAppendStrCh: binaryExpr(p, n, r, "addChar", - "if ($1 != null) { addChar($1, $2); } else { $1 = [$2, 0]; }") + "if ($1 != null) { addChar($1, $2); } else { $1 = [$2]; }") of mAppendStrStr: + var lhs, rhs: TCompRes + gen(p, n[1], lhs) + gen(p, n[2], rhs) + + let rhsIsLit = n[2].kind in nkStrKinds if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyCString: - binaryExpr(p, n, r, "", "if ($1 != null) { $1 += $2; } else { $1 = $2; }") + r.res = "if ($1 != null) { $1 += $2; } else { $1 = $2$3; }" % [ + lhs.rdLoc, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()"] else: - binaryExpr(p, n, r, "", - "if ($1 != null) { $1 = ($1.slice(0, -1)).concat($2); } else { $1 = $2;}") - # XXX: make a copy of $2, because of Javascript's sucking semantics + r.res = "if ($1 != null) { $1 = ($1).concat($2); } else { $1 = $2$3; }" % [ + lhs.rdLoc, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()"] + r.kind = resExpr of mAppendSeqElem: var x, y: TCompRes gen(p, n.sons[1], x) @@ -1682,21 +1699,12 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = of mSizeOf: r.res = rope(getSize(p.config, n.sons[1].typ)) of mChr, mArrToSeq: gen(p, n.sons[1], r) # nothing to do of mOrd: genOrd(p, n, r) - of mLengthStr: - if n.sons[1].typ.skipTypes(abstractInst).kind == tyCString: - unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)") - else: - unaryExpr(p, n, r, "", "($1 != null ? $1.length-1 : 0)") - of mXLenStr: unaryExpr(p, n, r, "", "$1.length-1") - of mLengthSeq, mLengthOpenArray, mLengthArray: + of mLengthStr, mLengthSeq, mLengthOpenArray, mLengthArray: unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)") - of mXLenSeq: + of mXLenStr, mXLenSeq: unaryExpr(p, n, r, "", "$1.length") of mHigh: - if skipTypes(n.sons[1].typ, abstractVar).kind == tyString: - unaryExpr(p, n, r, "", "($1 != null ? ($1.length-2) : -1)") - else: - unaryExpr(p, n, r, "", "($1 != null ? ($1.length-1) : -1)") + unaryExpr(p, n, r, "", "($1 != null ? ($1.length-1) : -1)") of mInc: if n[1].typ.skipTypes(abstractRange).kind in tyUInt .. tyUInt64: binaryUintExpr(p, n, r, "+", true) @@ -1710,7 +1718,7 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 -= $2") else: binaryExpr(p, n, r, "subInt", "$1 = subInt($1, $2)") of mSetLengthStr: - binaryExpr(p, n, r, "", "$1.length = $2+1; $1[$1.length-1] = 0") + binaryExpr(p, n, r, "", "$1.length = $2") of mSetLengthSeq: var x, y: TCompRes gen(p, n.sons[1], x) @@ -1739,8 +1747,6 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = localError(p.config, n.info, errXMustBeCompileTime % n.sons[0].sym.name.s) of mCopyStr: binaryExpr(p, n, r, "", "($1.slice($2))") - of mCopyStrLast: - ternaryExpr(p, n, r, "", "($1.slice($2, ($3)+1).concat(0))") of mNewString: unaryExpr(p, n, r, "mnewString", "mnewString($1)") of mNewStringOfCap: unaryExpr(p, n, r, "mnewString", "mnewString(0)") @@ -2065,8 +2071,11 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = r.kind = resExpr of nkStrLit..nkTripleStrLit: if skipTypes(n.typ, abstractVarRange).kind == tyString: - useMagic(p, "makeNimstrLit") - r.res = "makeNimstrLit($1)" % [makeJSString(n.strVal)] + if n.strVal.len != 0: + useMagic(p, "makeNimstrLit") + r.res = "makeNimstrLit($1)" % [makeJSString(n.strVal)] + else: + r.res = rope"[]" else: r.res = makeJSString(n.strVal, false) r.kind = resExpr @@ -2254,7 +2263,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 = @@ -2268,11 +2277,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/layouter.nim b/compiler/layouter.nim index 36ad08696..cc6ec48b7 100644 --- a/compiler/layouter.nim +++ b/compiler/layouter.nim @@ -9,7 +9,8 @@ ## Layouter for nimpretty. -import idents, lexer, lineinfos, llstream, options, msgs, strutils +import idents, lexer, lineinfos, llstream, options, msgs, strutils, + pathutils from os import changeFileExt const @@ -39,7 +40,7 @@ type proc openEmitter*(em: var Emitter, cache: IdentCache; config: ConfigRef, fileIdx: FileIndex) = - let fullPath = config.toFullPath(fileIdx) + let fullPath = Absolutefile config.toFullPath(fileIdx) em.indWidth = getIndentWidth(fileIdx, llStreamOpen(fullPath, fmRead), cache, config) if em.indWidth == 0: em.indWidth = 2 @@ -55,7 +56,7 @@ proc openEmitter*(em: var Emitter, cache: IdentCache; proc closeEmitter*(em: var Emitter) = var f = llStreamOpen(em.config.outFile, fmWrite) if f == nil: - rawMessage(em.config, errGenerated, "cannot open file: " & em.config.outFile) + rawMessage(em.config, errGenerated, "cannot open file: " & em.config.outFile.string) f.llStreamWrite em.content llStreamClose(f) diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 6ad1d9fc6..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) @@ -884,6 +884,42 @@ proc getOperator(L: var TLexer, tok: var TToken) = if buf[pos] in {CR, LF, nimlexbase.EndOfFile}: tok.strongSpaceB = -1 +proc getPrecedence*(tok: TToken, strongSpaces: bool): int = + ## Calculates the precedence of the given token. + template considerStrongSpaces(x): untyped = + x + (if strongSpaces: 100 - tok.strongSpaceA.int*10 else: 0) + + case tok.tokType + of tkOpr: + let L = tok.ident.s.len + let relevantChar = tok.ident.s[0] + + # arrow like? + if L > 1 and tok.ident.s[L-1] == '>' and + tok.ident.s[L-2] in {'-', '~', '='}: return considerStrongSpaces(1) + + template considerAsgn(value: untyped) = + result = if tok.ident.s[L-1] == '=': 1 else: value + + case relevantChar + of '$', '^': considerAsgn(10) + of '*', '%', '/', '\\': considerAsgn(9) + of '~': result = 8 + of '+', '-', '|': considerAsgn(8) + of '&': considerAsgn(7) + of '=', '<', '>', '!': result = 5 + of '.': considerAsgn(6) + of '?': result = 2 + else: considerAsgn(2) + of tkDiv, tkMod, tkShl, tkShr: result = 9 + of tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs: result = 5 + of tkDotDot: result = 6 + of tkAnd: result = 4 + of tkOr, tkXor, tkPtr, tkRef: result = 3 + else: return -10 + result = considerStrongSpaces(result) + + proc newlineFollows*(L: TLexer): bool = var pos = L.bufpos var buf = L.buf @@ -1249,3 +1285,14 @@ proc getIndentWidth*(fileIdx: FileIndex, inputstream: PLLStream; result = tok.indent if result > 0 or tok.tokType == tkEof: break closeLexer(lex) + +proc getPrecedence*(ident: PIdent): int = + ## assumes ident is binary operator already + var tok: TToken + initToken(tok) + tok.ident = ident + tok.tokType = + if tok.ident.id in ord(tokKeywordLow) - ord(tkSymbol) .. ord(tokKeywordHigh) - ord(tkSymbol): + TTokType(tok.ident.id + ord(tkSymbol)) + else: tkOpr + getPrecedence(tok, false) 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..e1108147f 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) @@ -87,9 +87,9 @@ proc endsWithOpr*(x: string): bool = result = x.endsWith(LineContinuationOprs) proc continueLine(line: string, inTripleString: bool): bool {.inline.} = - result = inTripleString or - line[0] == ' ' or - line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs) + result = inTripleString or line.len > 0 and ( + line[0] == ' ' or + line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs)) proc countTriples(s: string): int = var i = 0 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..d817b2956 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: + except OSError: 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) - except: + shallow(canon.string) + except OSError: 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..5f3347255 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,10 +31,10 @@ 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 @@ -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 4a484e6e2..a95d9930a 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 @@ -136,7 +136,7 @@ type External ## file was introduced via .compile pragma Cfile* = object - cname*, obj*: string + cname*, obj*: AbsoluteFile flags*: set[CFileFlag] CfileList* = seq[Cfile] @@ -204,13 +204,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) @@ -222,9 +223,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 (*) @@ -303,12 +304,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) @@ -402,7 +404,7 @@ template optPreserveOrigSource*(conf: ConfigRef): untyped = optEmbedOrigSrc in conf.globalOptions const - genSubDir* = "nimcache" + genSubDir* = RelativeDir"nimcache" NimExt* = "nim" RodExt* = "rod" HtmlExt* = "html" @@ -410,10 +412,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 @@ -438,56 +440,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 @@ -508,89 +515,89 @@ proc getOsCacheDir(): string = when defined(posix): result = getEnv("XDG_CACHE_HOME", getHomeDir() / ".cache") / "nim" else: - result = getHomeDir() / genSubDir + result = getHomeDir() / genSubDir.string -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 = [ @@ -600,7 +607,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/" @@ -611,13 +618,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 246dcb814..7667d3a0e 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) @@ -252,41 +253,6 @@ proc isRightAssociative(tok: TToken): bool {.inline.} = result = tok.tokType == tkOpr and tok.ident.s[0] == '^' # or (let L = tok.ident.s.len; L > 1 and tok.ident.s[L-1] == '>')) -proc getPrecedence(tok: TToken, strongSpaces: bool): int = - ## Calculates the precedence of the given token. - template considerStrongSpaces(x): untyped = - x + (if strongSpaces: 100 - tok.strongSpaceA.int*10 else: 0) - - case tok.tokType - of tkOpr: - let L = tok.ident.s.len - let relevantChar = tok.ident.s[0] - - # arrow like? - if L > 1 and tok.ident.s[L-1] == '>' and - tok.ident.s[L-2] in {'-', '~', '='}: return considerStrongSpaces(1) - - template considerAsgn(value: untyped) = - result = if tok.ident.s[L-1] == '=': 1 else: value - - case relevantChar - of '$', '^': considerAsgn(10) - of '*', '%', '/', '\\': considerAsgn(9) - of '~': result = 8 - of '+', '-', '|': considerAsgn(8) - of '&': considerAsgn(7) - of '=', '<', '>', '!': result = 5 - of '.': considerAsgn(6) - of '?': result = 2 - else: considerAsgn(2) - of tkDiv, tkMod, tkShl, tkShr: result = 9 - of tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs: result = 5 - of tkDotDot: result = 6 - of tkAnd: result = 4 - of tkOr, tkXor, tkPtr, tkRef: result = 3 - else: return -10 - result = considerStrongSpaces(result) - proc isOperator(tok: TToken): bool = ## Determines if the given token is an operator type token. tok.tokType in {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs, @@ -572,7 +538,7 @@ proc parsePar(p: var TParser): PNode = flexComment(p, result) if p.tok.tokType in {tkDiscard, tkInclude, tkIf, tkWhile, tkCase, tkTry, tkDefer, tkFinally, tkExcept, tkFor, tkBlock, - tkConst, tkLet, tkWhen, tkVar, + tkConst, tkLet, tkWhen, tkVar, tkFor, tkMixin}: # XXX 'bind' used to be an expression, so we exclude it here; # tests/reject/tbind2 fails otherwise. @@ -1147,7 +1113,7 @@ proc parseProcExpr(p: var TParser; isExpr: bool; kind: TNodeKind): PNode = proc isExprStart(p: TParser): bool = case p.tok.tokType - of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf, + of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf, tkFor, tkProc, tkFunc, tkIterator, tkBind, tkBuiltInMagics, tkParLe, tkBracketLe, tkCurlyLe, tkIntLit..tkCharLit, tkVar, tkRef, tkPtr, tkTuple, tkObject, tkWhen, tkCase, tkOut: @@ -1187,16 +1153,35 @@ proc parseTypeDescKAux(p: var TParser, kind: TNodeKind, result.addSon list parseSymbolList(p, list) +proc parseFor(p: var TParser): PNode = + #| forStmt = 'for' (identWithPragma ^+ comma) 'in' expr colcom stmt + #| forExpr = forStmt + result = newNodeP(nkForStmt, p) + getTokNoInd(p) + var a = identWithPragma(p) + addSon(result, a) + while p.tok.tokType == tkComma: + getTok(p) + optInd(p, a) + a = identWithPragma(p) + addSon(result, a) + eat(p, tkIn) + addSon(result, parseExpr(p)) + colcom(p, result) + addSon(result, parseStmt(p)) + proc parseExpr(p: var TParser): PNode = #| expr = (blockExpr #| | ifExpr #| | whenExpr #| | caseExpr + #| | forExpr #| | tryExpr) #| / simpleExpr case p.tok.tokType: of tkBlock: result = parseBlock(p) of tkIf: result = parseIfExpr(p, nkIfExpr) + of tkFor: result = parseFor(p) of tkWhen: result = parseIfExpr(p, nkWhenExpr) of tkCase: result = parseCase(p) of tkTry: result = parseTry(p, isExpr=true) @@ -1603,22 +1588,6 @@ proc parseExceptBlock(p: var TParser, kind: TNodeKind): PNode = colcom(p, result) addSon(result, parseStmt(p)) -proc parseFor(p: var TParser): PNode = - #| forStmt = 'for' (identWithPragma ^+ comma) 'in' expr colcom stmt - result = newNodeP(nkForStmt, p) - getTokNoInd(p) - var a = identWithPragma(p) - addSon(result, a) - while p.tok.tokType == tkComma: - getTok(p) - optInd(p, a) - a = identWithPragma(p) - addSon(result, a) - eat(p, tkIn) - addSon(result, parseExpr(p)) - colcom(p, result) - addSon(result, parseStmt(p)) - proc parseBlock(p: var TParser): PNode = #| blockStmt = 'block' symbol? colcom stmt #| blockExpr = 'block' symbol? colcom stmt @@ -2270,7 +2239,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..ef0fa4abd --- /dev/null +++ b/compiler/pathutils.nim @@ -0,0 +1,260 @@ +# +# +# 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 + if d > 0: setLen(result, d-1) + elif isDot(x, b): + discard "discard the dot" + elif b[1] > b[0]: + if result.len > 0 and result[^1] != DirSep: + 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 = cmpIgnoreCase(canon x, canon y) == 0 + 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" + + doAssert AbsoluteDir"/Users/me///" / RelativeFile"z.nim" == AbsoluteFile"/Users/me/z.nim" + doAssert relativeTo("/foo/bar.nim", "/foo/") == "bar.nim" + +when isMainModule and defined(windows): + let nasty = string(AbsoluteDir(r"C:\Users\rumpf\projects\nim\tests\nimble\nimbleDir\linkedPkgs\pkgB-#head\../../simplePkgs/pkgB-#head/") / RelativeFile"pkgA/module.nim") + doAssert nasty == r"C:\Users\rumpf\projects\nim\tests\nimble\nimbleDir\simplePkgs\pkgB-#head\pkgA\module.nim" diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 9bc3f353e..b8dae8123 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 @@ -53,7 +53,7 @@ const lambdaPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl, wNosideeffect, wSideeffect, wNoreturn, wDynlib, wHeader, wDeprecated, wExtern, wThread, wImportCpp, wImportObjC, wAsmNoStackFrame, - wRaises, wLocks, wTags, wGcSafe} + wRaises, wLocks, wTags, wGcSafe, wCodegenDecl} typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl, wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wExtern, wShallow, wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef, @@ -457,26 +457,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]) @@ -493,30 +489,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, "") @@ -609,8 +598,14 @@ 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) + if c.config.projectPath.isEmpty: + n.info.fileIndex = fileInfoIdx(c.config, AbsoluteFile(x.strVal)) + else: + # XXX this is still suspicous: + let dir = toFullPath(c.config, n.info).splitFile.dir + let rel = if isAbsolute(x.strVal): relativeTo(AbsoluteFile(x.strVal), c.config.projectPath) + else: RelativeFile(x.strVal) + n.info.fileIndex = fileInfoIdx(c.config, AbsoluteDir(dir) / rel) n.info.line = uint16(y.intVal) else: localError(c.config, n.info, "tuple expected") @@ -974,8 +969,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 a8f3f4afc..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 @@ -307,14 +309,19 @@ proc lsub(g: TSrcGen; n: PNode): int proc litAux(g: TSrcGen; n: PNode, x: BiggestInt, size: int): string = proc skip(t: PType): PType = result = t - while result.kind in {tyGenericInst, tyRange, tyVar, tyLent, tyDistinct, + while result != nil and result.kind in {tyGenericInst, tyRange, tyVar, tyLent, tyDistinct, tyOrdinal, tyAlias, tySink}: result = lastSon(result) - if n.typ != nil and n.typ.skip.kind in {tyBool, tyEnum}: - let enumfields = n.typ.skip.n + let typ = n.typ.skip + if typ != nil and typ.kind in {tyBool, tyEnum}: + if sfPure in typ.sym.flags: + result = typ.sym.name.s & '.' + let enumfields = typ.n # we need a slow linear search because of enums with holes: for e in items(enumfields): - if e.sym.position == x: return e.sym.name.s + if e.sym.position == x: + result &= e.sym.name.s + return if nfBase2 in n.flags: result = "0b" & toBin(x, size * 8) elif nfBase8 in n.flags: result = "0o" & toOct(x, size * 3) @@ -831,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) @@ -861,6 +868,47 @@ proc isBracket*(n: PNode): bool = of nkSym: result = n.sym.name.s == "[]" else: result = false +proc skipHiddenNodes(n: PNode): PNode = + result = n + while result != nil: + if result.kind in {nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv} and result.len > 1: + result = result[1] + elif result.kind in {nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString} and + result.len > 0: + result = result[0] + else: break + +proc accentedName(g: var TSrcGen, n: PNode) = + if n == nil: return + let isOperator = + if n.kind == nkIdent and n.ident.s.len > 0 and n.ident.s[0] in OpChars: true + elif n.kind == nkSym and n.sym.name.s.len > 0 and n.sym.name.s[0] in OpChars: true + else: false + + if isOperator: + put(g, tkAccent, "`") + gident(g, n) + put(g, tkAccent, "`") + else: + gsub(g, n) + +proc infixArgument(g: var TSrcGen, n: PNode, i: int) = + if i >= n.len: return + + var needsParenthesis = false + let n_next = n[i].skipHiddenNodes + if n_next.kind == nkInfix: + if n_next[0].kind in {nkSym, nkIdent} and n[0].kind in {nkSym, nkIdent}: + let nextId = if n_next[0].kind == nkSym: n_next[0].sym.name else: n_next[0].ident + let nnId = if n[0].kind == nkSym: n[0].sym.name else: n[0].ident + if getPrecedence(nextId) < getPrecedence(nnId): + needsParenthesis = true + if needsParenthesis: + put(g, tkParLe, "(") + gsub(g, n, i) + if needsParenthesis: + put(g, tkParRi, ")") + proc gsub(g: var TSrcGen, n: PNode, c: TContext) = if isNil(n): return var @@ -896,7 +944,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = gcomma(g, n, 2) put(g, tkBracketRi, "]") elif n.len > 1 and n.lastSon.kind == nkStmtList: - gsub(g, n[0]) + accentedName(g, n[0]) if n.len > 2: put(g, tkParLe, "(") gcomma(g, n, 1, -2) @@ -904,16 +952,16 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = put(g, tkColon, ":") gsub(g, n, n.len-1) else: - if sonsLen(n) >= 1: gsub(g, n.sons[0]) + if sonsLen(n) >= 1: accentedName(g, n[0]) put(g, tkParLe, "(") gcomma(g, n, 1) put(g, tkParRi, ")") of nkCallStrLit: - gsub(g, n, 0) + if n.len > 0: accentedName(g, n[0]) if n.len > 1 and n.sons[1].kind == nkRStrLit: put(g, tkRStrLit, '\"' & replace(n[1].strVal, "\"", "\"\"") & '\"') else: - gsub(g, n.sons[1]) + gsub(g, n, 1) of nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv: if n.len >= 2: gsub(g, n.sons[1]) @@ -951,7 +999,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = gsub(g, n, 0) gcomma(g, n, 1) of nkCommand: - gsub(g, n, 0) + accentedName(g, n[0]) put(g, tkSpaces, Space) gcomma(g, n, 1) of nkExprEqExpr, nkAsgn, nkFastAsgn: @@ -1064,14 +1112,14 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = putWithSpace(g, tkColon, ":") gsub(g, n, 1) of nkInfix: - gsub(g, n, 1) + infixArgument(g, n, 1) put(g, tkSpaces, Space) gsub(g, n, 0) # binary operator if not fits(g, lsub(g, n.sons[2]) + lsub(g, n.sons[0]) + 1): optNL(g, g.indent + longIndentWid) else: put(g, tkSpaces, Space) - gsub(g, n, 2) + infixArgument(g, n, 2) of nkPrefix: gsub(g, n, 0) if n.len > 1: @@ -1079,10 +1127,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = elif n[0].kind == nkSym: n[0].sym.name elif n[0].kind in {nkOpenSymChoice, nkClosedSymChoice}: n[0][0].sym.name else: nil - var n_next = n[1] - while n_next.kind in {nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, - nkStringToCString, nkCStringToString} and n_next.len > 0: - n_next = n_next[0] + let n_next = skipHiddenNodes(n[1]) if n_next.kind == nkPrefix or (opr != nil and renderer.isKeyword(opr)): put(g, tkSpaces, Space) if n_next.kind == nkInfix: @@ -1498,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/sem.nim b/compiler/sem.nim index 7a83c3079..5e5205c20 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -37,7 +37,7 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode proc semTypeNode(c: PContext, n: PNode, prev: PType): PType -proc semStmt(c: PContext, n: PNode): PNode +proc semStmt(c: PContext, n: PNode; flags: TExprFlags): PNode proc semOpAux(c: PContext, n: PNode) proc semParamList(c: PContext, n, genericParams: PNode, s: PSym) proc addParams(c: PContext, n: PNode, kind: TSymKind) @@ -399,7 +399,7 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode, excl(result.flags, nfSem) #resetSemFlag n if s.typ.sons[0] == nil: - result = semStmt(c, result) + result = semStmt(c, result, flags) else: case s.typ.sons[0].kind of tyExpr: @@ -408,7 +408,7 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode, # semExprWithType(c, result) result = semExpr(c, result, flags) of tyStmt: - result = semStmt(c, result) + result = semStmt(c, result, flags) of tyTypeDesc: if result.kind == nkStmtList: result.kind = nkStmtListType var typ = semTypeNode(c, result, nil) @@ -557,7 +557,7 @@ proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode = result = semAllTypeSections(c, n) else: result = n - result = semStmt(c, result) + result = semStmt(c, result, {}) when false: # Code generators are lazy now and can deal with undeclared procs, so these # steps are not required anymore and actually harmful for the upcoming diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index ce953f17c..43f04fc9f 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -113,6 +113,7 @@ proc checkConvertible(c: PContext, castDest, src: PType): TConvStatus = if castDest.kind notin IntegralTypes+{tyRange}: result = convNotNeedeed return + # Save for later var d = skipTypes(castDest, abstractVar) var s = src if s.kind in tyUserTypeClasses and s.isResolvedUserTypeClass: @@ -135,7 +136,7 @@ proc checkConvertible(c: PContext, castDest, src: PType): TConvStatus = # we use d, s here to speed up that operation a bit: case cmpTypes(c, d, s) of isNone, isGeneric: - if not compareTypes(castDest, src, dcEqIgnoreDistinct): + if not compareTypes(castDest.skipTypes(abstractVar), src, dcEqIgnoreDistinct): result = convNotLegal else: discard @@ -917,7 +918,7 @@ proc semExprNoType(c: PContext, n: PNode): PNode = let isPush = hintExtendedContext in c.config.notes if isPush: pushInfoContext(c.config, n.info) result = semExpr(c, n, {efWantStmt}) - discardCheck(c, result) + discardCheck(c, result, {}) if isPush: popInfoContext(c.config) proc isTypeExpr(n: PNode): bool = @@ -1529,7 +1530,7 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = # unfortunately we need to rewrite ``(x, y) = foo()`` already here so # that overloading of the assignment operator still works. Usually we # prefer to do these rewritings in transf.nim: - return semStmt(c, lowerTupleUnpackingForAsgn(c.graph, n, c.p.owner)) + return semStmt(c, lowerTupleUnpackingForAsgn(c.graph, n, c.p.owner), {}) else: a = semExprWithType(c, a, {efLValue}) else: @@ -1613,7 +1614,7 @@ proc semProcBody(c: PContext, n: PNode): PNode = a.sons[1] = result result = semAsgn(c, a) else: - discardCheck(c, result) + discardCheck(c, result, {}) if c.p.owner.kind notin {skMacro, skTemplate} and c.p.resultSym != nil and c.p.resultSym.typ.isMetaType: @@ -1989,7 +1990,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = var x = n.lastSon if x.kind == nkDo: x = x.sons[bodyPos] inc c.inParallelStmt - result.sons[1] = semStmt(c, x) + result.sons[1] = semStmt(c, x, {}) dec c.inParallelStmt of mSpawn: result = setMs(n, s) @@ -2240,7 +2241,7 @@ proc isTupleType(n: PNode): bool = include semobjconstr -proc semBlock(c: PContext, n: PNode): PNode = +proc semBlock(c: PContext, n: PNode; flags: TExprFlags): PNode = result = n inc(c.p.nestedBlockCounter) checkSonsLen(n, 2, c.config) @@ -2252,7 +2253,7 @@ proc semBlock(c: PContext, n: PNode): PNode = n.sons[0] = newSymNode(labl, n.sons[0].info) suggestSym(c.config, n.sons[0].info, labl, c.graph.usageSym) styleCheckDef(c.config, labl) - n.sons[1] = semExpr(c, n.sons[1]) + n.sons[1] = semExpr(c, n.sons[1], flags) n.typ = n.sons[1].typ if isEmptyType(n.typ): n.kind = nkBlockStmt else: n.kind = nkBlockExpr @@ -2497,7 +2498,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = checkSonsLen(n, 1, c.config) n.sons[0] = semExpr(c, n.sons[0], flags) of nkCast: result = semCast(c, n) - of nkIfExpr, nkIfStmt: result = semIf(c, n) + of nkIfExpr, nkIfStmt: result = semIf(c, n, flags) of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkHiddenCallConv: checkSonsLen(n, 2, c.config) considerGenSyms(c, n) @@ -2518,7 +2519,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = discard of nkStaticExpr: result = semStaticExpr(c, n[0]) of nkAsgn: result = semAsgn(c, n) - of nkBlockStmt, nkBlockExpr: result = semBlock(c, n) + of nkBlockStmt, nkBlockExpr: result = semBlock(c, n, flags) of nkStmtList, nkStmtListExpr: result = semStmtList(c, n, flags) of nkRaiseStmt: result = semRaise(c, n) of nkVarSection: result = semVarOrLet(c, n, skVar) @@ -2526,11 +2527,11 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkConstSection: result = semConst(c, n) of nkTypeSection: result = semTypeSection(c, n) of nkDiscardStmt: result = semDiscard(c, n) - of nkWhileStmt: result = semWhile(c, n) - of nkTryStmt: result = semTry(c, n) + of nkWhileStmt: result = semWhile(c, n, flags) + of nkTryStmt: result = semTry(c, n, flags) of nkBreakStmt, nkContinueStmt: result = semBreakOrContinue(c, n) - of nkForStmt, nkParForStmt: result = semFor(c, n) - of nkCaseStmt: result = semCase(c, n) + of nkForStmt, nkParForStmt: result = semFor(c, n, flags) + of nkCaseStmt: result = semCase(c, n, flags) of nkReturnStmt: result = semReturn(c, n) of nkUsingStmt: result = semUsing(c, n) of nkAsmStmt: result = semAsm(c, n) diff --git a/compiler/semfields.nim b/compiler/semfields.nim index 869f5ae74..07321f477 100644 --- a/compiler/semfields.nim +++ b/compiler/semfields.nim @@ -70,7 +70,7 @@ proc semForObjectFields(c: TFieldsCtx, typ, forLoop, father: PNode) = openScope(c.c) inc c.c.inUnrolledContext let body = instFieldLoopBody(fc, lastSon(forLoop), forLoop) - father.add(semStmt(c.c, body)) + father.add(semStmt(c.c, body, {})) dec c.c.inUnrolledContext closeScope(c.c) of nkNilLit: discard @@ -145,7 +145,7 @@ proc semForFields(c: PContext, n: PNode, m: TMagic): PNode = fc.replaceByFieldName = m == mFieldPairs var body = instFieldLoopBody(fc, loopBody, n) inc c.inUnrolledContext - stmts.add(semStmt(c, body)) + stmts.add(semStmt(c, body, {})) dec c.inUnrolledContext closeScope(c) else: diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 27a6af1f4..0018f0755 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -214,7 +214,24 @@ proc evalIs(n: PNode, lhs: PSym, g: ModuleGraph): PNode = result = newIntNode(nkIntLit, ord(res)) result.typ = n.typ +proc fitLiteral(c: ConfigRef, n: PNode): PNode = + # Trim the literal value in order to make it fit in the destination type + if n == nil: + # `n` may be nil if the overflow check kicks in + return + + doAssert n.kind in {nkIntLit, nkCharLit} + + result = n + + let typ = n.typ.skipTypes(abstractRange) + if typ.kind in tyUInt..tyUint32: + result.intVal = result.intVal and lastOrd(c, typ, fixedUnsigned=true) + proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode = + template doAndFit(op: untyped): untyped = + # Implements wrap-around behaviour for unsigned types + fitLiteral(g.config, op) # b and c may be nil result = nil case m @@ -224,12 +241,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode = of mUnaryMinusF64: result = newFloatNodeT(- getFloat(a), n, g) of mNot: result = newIntNodeT(1 - getInt(a), n, g) of mCard: result = newIntNodeT(nimsets.cardSet(g.config, a), n, g) - of mBitnotI: - case skipTypes(n.typ, abstractRange).kind - of tyUInt..tyUInt64: - result = newIntNodeT((not getInt(a)) and lastOrd(g.config, a.typ, fixedUnsigned=true), n, g) - else: - result = newIntNodeT(not getInt(a), n, g) + of mBitnotI: result = doAndFit(newIntNodeT(not getInt(a), n, g)) of mLengthArray: result = newIntNodeT(lengthOrd(g.config, a.typ), n, g) of mLengthSeq, mLengthOpenArray, mXLenSeq, mLengthStr, mXLenStr: if a.kind == nkNilLit: @@ -251,9 +263,9 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode = of mToU8: result = newIntNodeT(getInt(a) and 0x000000FF, n, g) of mToU16: result = newIntNodeT(getInt(a) and 0x0000FFFF, n, g) of mToU32: result = newIntNodeT(getInt(a) and 0x00000000FFFFFFFF'i64, n, g) - of mUnaryLt: result = foldSub(getOrdValue(a), 1, n, g) - of mSucc: result = foldAdd(getOrdValue(a), getInt(b), n, g) - of mPred: result = foldSub(getOrdValue(a), getInt(b), n, g) + of mUnaryLt: result = doAndFit(foldSub(getOrdValue(a), 1, n, g)) + of mSucc: result = doAndFit(foldAdd(getOrdValue(a), getInt(b), n, g)) + of mPred: result = doAndFit(foldSub(getOrdValue(a), getInt(b), n, g)) of mAddI: result = foldAdd(getInt(a), getInt(b), n, g) of mSubI: result = foldSub(getInt(a), getInt(b), n, g) of mMulI: result = foldMul(getInt(a), getInt(b), n, g) @@ -271,7 +283,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode = of tyInt64, tyInt: result = newIntNodeT(`shl`(getInt(a), getInt(b)), n, g) of tyUInt..tyUInt64: - result = newIntNodeT(`shl`(getInt(a), getInt(b)) and lastOrd(g.config, a.typ, fixedUnsigned=true), n, g) + result = doAndFit(newIntNodeT(`shl`(getInt(a), getInt(b)), n, g)) else: internalError(g.config, n.info, "constant folding for shl") of mShrI: case skipTypes(n.typ, abstractRange).kind @@ -324,14 +336,14 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode = result = newIntNodeT(ord(`<%`(getOrdValue(a), getOrdValue(b))), n, g) of mLeU, mLeU64: result = newIntNodeT(ord(`<=%`(getOrdValue(a), getOrdValue(b))), n, g) - of mBitandI, mAnd: result = newIntNodeT(a.getInt and b.getInt, n, g) - of mBitorI, mOr: result = newIntNodeT(getInt(a) or getInt(b), n, g) - of mBitxorI, mXor: result = newIntNodeT(a.getInt xor b.getInt, n, g) - of mAddU: result = newIntNodeT(`+%`(getInt(a), getInt(b)), n, g) - of mSubU: result = newIntNodeT(`-%`(getInt(a), getInt(b)), n, g) - of mMulU: result = newIntNodeT(`*%`(getInt(a), getInt(b)), n, g) - of mModU: result = foldModU(getInt(a), getInt(b), n, g) - of mDivU: result = foldDivU(getInt(a), getInt(b), n, g) + of mBitandI, mAnd: result = doAndFit(newIntNodeT(a.getInt and b.getInt, n, g)) + of mBitorI, mOr: result = doAndFit(newIntNodeT(getInt(a) or getInt(b), n, g)) + of mBitxorI, mXor: result = doAndFit(newIntNodeT(a.getInt xor b.getInt, n, g)) + of mAddU: result = doAndFit(newIntNodeT(`+%`(getInt(a), getInt(b)), n, g)) + of mSubU: result = doAndFit(newIntNodeT(`-%`(getInt(a), getInt(b)), n, g)) + of mMulU: result = doAndFit(newIntNodeT(`*%`(getInt(a), getInt(b)), n, g)) + of mModU: result = doAndFit(foldModU(getInt(a), getInt(b), n, g)) + of mDivU: result = doAndFit(foldDivU(getInt(a), getInt(b), n, g)) of mLeSet: result = newIntNodeT(ord(containsSets(g.config, a, b)), n, g) of mEqSet: result = newIntNodeT(ord(equalSets(g.config, a, b)), n, g) of mLtSet: @@ -462,17 +474,24 @@ proc foldConv(n, a: PNode; g: ModuleGraph; check = false): PNode = result = newIntNodeT(int(getFloat(a)), n, g) of tyChar: result = newIntNodeT(getOrdValue(a), n, g) - of tyUInt8..tyUInt32, tyInt8..tyInt32: - let fromSigned = srcTyp.kind in tyInt..tyInt64 + of tyUInt..tyUInt64, tyInt..tyInt64: let toSigned = dstTyp.kind in tyInt..tyInt64 - - let mask = lastOrd(g.config, dstTyp, fixedUnsigned=true) - - var val = - if toSigned: - a.getOrdValue mod mask - else: - a.getOrdValue and mask + var val = a.getOrdValue + + if dstTyp.kind in {tyInt, tyInt64, tyUint, tyUInt64}: + # No narrowing needed + discard + elif dstTyp.kind in {tyInt..tyInt64}: + # Signed type: Overflow check (if requested) and conversion + if check: rangeCheck(n, val, g) + let mask = (`shl`(1, getSize(g.config, dstTyp) * 8) - 1) + let valSign = val < 0 + val = abs(val) and mask + if valSign: val = -val + else: + # Unsigned type: Conversion + let mask = (`shl`(1, getSize(g.config, dstTyp) * 8) - 1) + val = val and mask result = newIntNodeT(val, n, g) else: diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index fb01127fc..566b634af 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -76,17 +76,19 @@ proc semAsm(c: PContext, n: PNode): PNode = if marker == '\0': marker = '`' # default marker result = semAsmOrEmit(c, n, marker) -proc semWhile(c: PContext, n: PNode): PNode = +proc semWhile(c: PContext, n: PNode; flags: TExprFlags): PNode = result = n checkSonsLen(n, 2, c.config) openScope(c) n.sons[0] = forceBool(c, semExprWithType(c, n.sons[0])) inc(c.p.nestedLoopCounter) - n.sons[1] = semStmt(c, n.sons[1]) + n.sons[1] = semStmt(c, n.sons[1], flags) dec(c.p.nestedLoopCounter) closeScope(c) if n.sons[1].typ == c.enforceVoidContext: result.typ = c.enforceVoidContext + elif efInTypeof in flags: + result.typ = n[1].typ proc toCover(c: PContext, t: PType): BiggestInt = let t2 = skipTypes(t, abstractVarRange-{tyTypeDesc}) @@ -97,8 +99,8 @@ proc toCover(c: PContext, t: PType): BiggestInt = proc semProc(c: PContext, n: PNode): PNode -proc semExprBranch(c: PContext, n: PNode): PNode = - result = semExpr(c, n) +proc semExprBranch(c: PContext, n: PNode; flags: TExprFlags = {}): PNode = + result = semExpr(c, n, flags) if result.typ != nil: # XXX tyGenericInst here? if result.typ.kind in {tyVar, tyLent}: result = newDeref(result) @@ -130,8 +132,9 @@ proc fixNilType(c: PContext; n: PNode) = for it in n: fixNilType(c, it) n.typ = nil -proc discardCheck(c: PContext, result: PNode) = - if c.matchedConcept != nil: return +proc discardCheck(c: PContext, result: PNode, flags: TExprFlags) = + if c.matchedConcept != nil or efInTypeof in flags: return + if result.typ != nil and result.typ.kind notin {tyStmt, tyVoid}: if implicitlyDiscardable(result): var n = newNodeI(nkDiscardStmt, result.info, 1) @@ -148,7 +151,7 @@ proc discardCheck(c: PContext, result: PNode) = s.add "; for a function call use ()" localError(c.config, n.info, s) -proc semIf(c: PContext, n: PNode): PNode = +proc semIf(c: PContext, n: PNode; flags: TExprFlags): PNode = result = n var typ = commonTypeBegin var hasElse = false @@ -165,8 +168,9 @@ proc semIf(c: PContext, n: PNode): PNode = it.sons[0] = semExprBranchScope(c, it.sons[0]) typ = commonType(typ, it.sons[0]) else: illFormedAst(it, c.config) - if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse: - for it in n: discardCheck(c, it.lastSon) + if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or + (not hasElse and efInTypeof notin flags): + for it in n: discardCheck(c, it.lastSon, flags) result.kind = nkIfStmt # propagate any enforced VoidContext: if typ == c.enforceVoidContext: result.typ = c.enforceVoidContext @@ -178,8 +182,7 @@ proc semIf(c: PContext, n: PNode): PNode = result.kind = nkIfExpr result.typ = typ -proc semTry(c: PContext, n: PNode): PNode = - +proc semTry(c: PContext, n: PNode; flags: TExprFlags): PNode = var check = initIntSet() template semExceptBranchType(typeNode: PNode): bool = # returns true if exception type is imported type @@ -246,12 +249,12 @@ proc semTry(c: PContext, n: PNode): PNode = dec c.p.inTryStmt if isEmptyType(typ) or typ.kind in {tyNil, tyExpr}: - discardCheck(c, n.sons[0]) - for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon) + discardCheck(c, n.sons[0], flags) + for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon, flags) if typ == c.enforceVoidContext: result.typ = c.enforceVoidContext else: - if n.lastSon.kind == nkFinally: discardCheck(c, n.lastSon.lastSon) + if n.lastSon.kind == nkFinally: discardCheck(c, n.lastSon.lastSon, flags) n.sons[0] = fitNode(c, typ, n.sons[0], n.sons[0].info) for i in 1..last: var it = n.sons[i] @@ -570,7 +573,7 @@ proc symForVar(c: PContext, n: PNode): PSym = if n.kind == nkPragmaExpr: pragma(c, result, n.sons[1], forVarPragmas) -proc semForVars(c: PContext, n: PNode): PNode = +proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode = result = n var length = sonsLen(n) let iterBase = n.sons[length-2].typ @@ -601,7 +604,7 @@ proc semForVars(c: PContext, n: PNode): PNode = addForVarDecl(c, v) inc(c.p.nestedLoopCounter) openScope(c) - n.sons[length-1] = semStmt(c, n.sons[length-1]) + n.sons[length-1] = semExprBranch(c, n.sons[length-1], flags) closeScope(c) dec(c.p.nestedLoopCounter) @@ -685,7 +688,7 @@ proc handleCaseStmtMacro(c: PContext; n: PNode): PNode = when false: result = handleStmtMacro(c, n, n[0], "CaseStmt") -proc semFor(c: PContext, n: PNode): PNode = +proc semFor(c: PContext, n: PNode; flags: TExprFlags): PNode = checkMinSonsLen(n, 3, c.config) var length = sonsLen(n) if forLoopMacros in c.features: @@ -702,14 +705,14 @@ proc semFor(c: PContext, n: PNode): PNode = if isCallExpr and call[0].kind == nkSym and call[0].sym.magic in {mFields, mFieldPairs, mOmpParFor}: if call.sons[0].sym.magic == mOmpParFor: - result = semForVars(c, n) + result = semForVars(c, n, flags) result.kind = nkParForStmt else: result = semForFields(c, n, call.sons[0].sym.magic) elif isCallExpr and call.sons[0].typ.callConv == ccClosure and tfIterator in call.sons[0].typ.flags: # first class iterator: - result = semForVars(c, n) + result = semForVars(c, n, flags) elif not isCallExpr or call.sons[0].kind != nkSym or call.sons[0].sym.kind != skIterator: if length == 3: @@ -718,15 +721,17 @@ proc semFor(c: PContext, n: PNode): PNode = n.sons[length-2] = implicitIterator(c, "pairs", n.sons[length-2]) else: localError(c.config, n.sons[length-2].info, "iterator within for loop context expected") - result = semForVars(c, n) + result = semForVars(c, n, flags) else: - result = semForVars(c, n) + result = semForVars(c, n, flags) # propagate any enforced VoidContext: if n.sons[length-1].typ == c.enforceVoidContext: result.typ = c.enforceVoidContext + elif efInTypeof in flags: + result.typ = result.lastSon.typ closeScope(c) -proc semCase(c: PContext, n: PNode): PNode = +proc semCase(c: PContext, n: PNode; flags: TExprFlags): PNode = result = n checkMinSonsLen(n, 2, c.config) openScope(c) @@ -782,8 +787,9 @@ proc semCase(c: PContext, n: PNode): PNode = else: localError(c.config, n.info, "not all cases are covered") closeScope(c) - if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse: - for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon) + if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or + (not hasElse and efInTypeof notin flags): + for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon, flags) # propagate any enforced VoidContext: if typ == c.enforceVoidContext: result.typ = c.enforceVoidContext @@ -1173,6 +1179,9 @@ proc semBorrow(c: PContext, n: PNode, s: PSym) = if b != nil: # store the alias: n.sons[bodyPos] = newSymNode(b) + # Carry over the original symbol magic, this is necessary in order to ensure + # the semantic pass is correct + s.magic = b.magic else: localError(c.config, n.info, errNoSymbolToBorrowFromFound) @@ -1792,7 +1801,7 @@ proc evalInclude(c: PContext, n: PNode): PNode = if containsOrIncl(c.includedFiles, f.int): localError(c.config, n.info, errRecursiveDependencyX % toFilename(c.config, f)) else: - addSon(result, semStmt(c, c.graph.includeFileCallback(c.graph, c.module, f))) + addSon(result, semStmt(c, c.graph.includeFileCallback(c.graph, c.module, f), {})) excl(c.includedFiles, f.int) proc setLine(n: PNode, info: TLineInfo) = @@ -1819,7 +1828,7 @@ proc semStaticStmt(c: PContext, n: PNode): PNode = #writeStackTrace() inc c.inStaticContext openScope(c) - let a = semStmt(c, n.sons[0]) + let a = semStmt(c, n.sons[0], {}) closeScope(c) dec c.inStaticContext n.sons[0] = a @@ -1895,11 +1904,11 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = if n.sons[i].typ == c.enforceVoidContext: #or usesResult(n.sons[i]): voidContext = true n.typ = c.enforceVoidContext - if i == last and (length == 1 or efWantValue in flags): + if i == last and (length == 1 or ({efWantValue, efInTypeof} * flags != {})): n.typ = n.sons[i].typ if not isEmptyType(n.typ): n.kind = nkStmtListExpr elif i != last or voidContext: - discardCheck(c, n.sons[i]) + discardCheck(c, n.sons[i], flags) else: n.typ = n.sons[i].typ if not isEmptyType(n.typ): n.kind = nkStmtListExpr @@ -1928,6 +1937,8 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = # it is an old-style comment statement: we replace it with 'discard ""': prettybase.replaceComment(result.info) -proc semStmt(c: PContext, n: PNode): PNode = - # now: simply an alias: - result = semExprNoType(c, n) +proc semStmt(c: PContext, n: PNode; flags: TExprFlags): PNode = + if efInTypeof notin flags: + result = semExprNoType(c, n) + else: + result = semExpr(c, n, flags) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 86f3a17ab..a90a06150 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1161,7 +1161,7 @@ proc semStmtListType(c: PContext, n: PNode, prev: PType): PType = checkMinSonsLen(n, 1, c.config) var length = sonsLen(n) for i in countup(0, length - 2): - n.sons[i] = semStmt(c, n.sons[i]) + n.sons[i] = semStmt(c, n.sons[i], {}) if length > 0: result = semTypeNode(c, n.sons[length - 1], prev) n.typ = result @@ -1406,6 +1406,13 @@ proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType = result.rawAddSon(base) result.flags.incl tfHasStatic +proc semTypeof(c: PContext; n: PNode; prev: PType): PType = + openScope(c) + let t = semExprWithType(c, n, {efInTypeof}) + closeScope(c) + fixupTypeOf(c, prev, t) + result = t.typ + proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = nil inc c.inTypeContext @@ -1416,9 +1423,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of nkTypeOfExpr: # for ``type(countup(1,3))``, see ``tests/ttoseq``. checkSonsLen(n, 1, c.config) - let typExpr = semExprWithType(c, n.sons[0], {efInTypeof}) - fixupTypeOf(c, prev, typExpr) - result = typExpr.typ + result = semTypeof(c, n.sons[0], prev) if result.kind == tyTypeDesc: result.flags.incl tfExplicit of nkPar: if sonsLen(n) == 1: result = semTypeNode(c, n.sons[0], prev) @@ -1487,9 +1492,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = semAnyRef(c, n, tyRef, prev) elif op.id == ord(wType): checkSonsLen(n, 2, c.config) - let typExpr = semExprWithType(c, n.sons[1], {efInTypeof}) - fixupTypeOf(c, prev, typExpr) - result = typExpr.typ + result = semTypeof(c, n[1], prev) else: if c.inGenericContext > 0 and n.kind == nkCall: result = makeTypeFromExpr(c, n.copyTree) diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index c315cbebb..a6067dfc9 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -367,8 +367,11 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType = # can come here for tyGenericInst too, see tests/metatype/ttypeor.nim # need to look into this issue later assert newbody.kind in {tyRef, tyPtr} - assert newbody.lastSon.typeInst == nil - newbody.lastSon.typeInst = result + if newbody.lastSon.typeInst != nil: + #internalError(cl.c.config, cl.info, "ref already has a 'typeInst' field") + discard + else: + newbody.lastSon.typeInst = result cl.c.typesWithOps.add((newbody, result)) let methods = skipTypes(bbody, abstractPtrs).methods for col, meth in items(methods): diff --git a/compiler/sighashes.nim b/compiler/sighashes.nim index 8f95175e5..218011b1d 100644 --- a/compiler/sighashes.nim +++ b/compiler/sighashes.nim @@ -68,6 +68,8 @@ else: toBase64a(cast[cstring](unsafeAddr u), sizeof(u)) proc `&=`(c: var MD5Context, s: string) = md5Update(c, s, s.len) proc `&=`(c: var MD5Context, ch: char) = md5Update(c, unsafeAddr ch, 1) + proc `&=`(c: var MD5Context, r: Rope) = + for l in leaves(r): md5Update(c, l, l.len) proc `&=`(c: var MD5Context, i: BiggestInt) = md5Update(c, cast[cstring](unsafeAddr i), sizeof(i)) @@ -173,19 +175,23 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) = c.hashSym(t.sym) of tyObject, tyEnum: if t.typeInst != nil: - assert t.typeInst.kind == tyGenericInst - for i in countup(0, sonsLen(t.typeInst) - 2): - c.hashType t.typeInst.sons[i], flags + # prevent against infinite recursions here, see bug #8883: + let inst = t.typeInst + t.typeInst = nil + assert inst.kind == tyGenericInst + for i in countup(0, inst.len - 2): + c.hashType inst.sons[i], flags + t.typeInst = inst return c &= char(t.kind) # Every cyclic type in Nim need to be constructed via some 't.sym', so this # is actually safe without an infinite recursion check: if t.sym != nil: - #if "Future:" in t.sym.name.s and t.typeInst == nil: - # writeStackTrace() - # echo "yes ", t.sym.name.s - # #quit 1 - if CoOwnerSig in flags: + if {sfCompilerProc} * t.sym.flags != {}: + doAssert t.sym.loc.r != nil + # The user has set a specific name for this type + c &= t.sym.loc.r + elif CoOwnerSig in flags: c.hashTypeSym(t.sym) else: c.hashSym(t.sym) 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/transf.nim b/compiler/transf.nim index 84297aa6a..347df3e49 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -624,7 +624,11 @@ proc transformCase(c: PTransf, n: PNode): PTransNode = case it.kind of nkElifBranch: if ifs.PNode == nil: - ifs = newTransNode(nkIfStmt, it.info, 0) + # Generate the right node depending on whether `n` is used as a stmt or + # as an expr + let kind = if n.typ != nil: nkIfExpr else: nkIfStmt + ifs = newTransNode(kind, it.info, 0) + ifs.PNode.typ = n.typ ifs.add(e) of nkElse: if ifs.PNode == nil: result.add(e) 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) |