diff options
41 files changed, 797 insertions, 667 deletions
diff --git a/.gitignore b/.gitignore index 57b8a68d4..f5b8e4826 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ tags install.sh deinstall.sh +doc/html/ doc/*.html doc/*.pdf doc/*.idx diff --git a/bootstrap.sh b/bootstrap.sh deleted file mode 100644 index 5d05ddbf8..000000000 --- a/bootstrap.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh -set -e -set -x - -if [ ! -e csources/.git ]; then - git clone --depth 1 https://github.com/nim-lang/csources.git csources -fi - -cd "csources" -sh build.sh -cd ".." - -./bin/nim c koch -./koch boot -d:release -./koch geninstall - -set +x - -echo -echo 'Install Nim using "./install.sh <dir>" or "sudo ./install.sh <dir>".' diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index be49ddc87..00a60fe1b 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -592,9 +592,9 @@ proc genEqProc(p: BProc, e: PNode, d: var TLoc) = proc genIsNil(p: BProc, e: PNode, d: var TLoc) = let t = skipTypes(e.sons[1].typ, abstractRange) if t.kind == tyProc and t.callConv == ccClosure: - unaryExpr(p, e, d, "$1.ClPrc == 0") + unaryExpr(p, e, d, "($1.ClPrc == 0)") else: - unaryExpr(p, e, d, "$1 == 0") + unaryExpr(p, e, d, "($1 == 0)") proc unaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) = const diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 86b0d60d0..6e18c8389 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -16,6 +16,8 @@ import condsyms, rodutils, renderer, idgen, cgendata, ccgmerge, semfold, aliases, lowerings, semparallel +from modulegraphs import ModuleGraph + import strutils except `%` # collides with ropes.`%` when options.hasTinyCBackend: @@ -1174,7 +1176,7 @@ proc newModule(module: PSym): BModule = if (sfDeadCodeElim in module.flags): internalError("added pending module twice: " & module.filename) -proc myOpen(module: PSym; cache: IdentCache): PPassContext = +proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = result = newModule(module) if optGenIndex in gGlobalOptions and generatedHeader == nil: let f = if headerFile.len > 0: headerFile else: gProjectFull @@ -1209,7 +1211,7 @@ proc getCFile(m: BModule): string = else: ".c" result = changeFileExt(completeCFilePath(m.cfilename.withPackageName), ext) -proc myOpenCached(module: PSym, rd: PRodReader): PPassContext = +proc myOpenCached(graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext = assert optSymbolFiles in gGlobalOptions var m = newModule(module) readMergeInfo(getCFile(m), m) diff --git a/compiler/depends.nim b/compiler/depends.nim index f8b8a3d71..9087f89f2 100644 --- a/compiler/depends.nim +++ b/compiler/depends.nim @@ -12,6 +12,8 @@ import os, options, ast, astalgo, msgs, ropes, idents, passes, importer +from modulegraphs import ModuleGraph + proc generateDot*(project: string) type @@ -46,7 +48,7 @@ proc generateDot(project: string) = rope(changeFileExt(extractFilename(project), "")), gDotGraph], changeFileExt(project, "dot")) -proc myOpen(module: PSym; cache: IdentCache): PPassContext = +proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = var g: PGen new(g) g.module = module diff --git a/compiler/docgen2.nim b/compiler/docgen2.nim index 17b6caaba..9504ab52f 100644 --- a/compiler/docgen2.nim +++ b/compiler/docgen2.nim @@ -13,6 +13,8 @@ import os, options, ast, astalgo, msgs, ropes, idents, passes, docgen +from modulegraphs import ModuleGraph + type TGen = object of TPassContext doc: PDoc @@ -49,7 +51,7 @@ proc processNodeJson(c: PPassContext, n: PNode): PNode = var g = PGen(c) generateJson(g.doc, n) -proc myOpen(module: PSym; cache: IdentCache): PPassContext = +proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = var g: PGen new(g) g.module = module diff --git a/compiler/importer.nim b/compiler/importer.nim index 771ab35d0..ce365c4dc 100644 --- a/compiler/importer.nim +++ b/compiler/importer.nim @@ -162,7 +162,7 @@ proc importModuleAs(n: PNode, realModule: PSym): PSym = proc myImportModule(c: PContext, n: PNode): PSym = var f = checkModuleName(n) if f != InvalidFileIDX: - result = importModuleAs(n, gImportModule(c.module, f, c.cache)) + result = importModuleAs(n, gImportModule(c.graph, c.module, f, c.cache)) # we cannot perform this check reliably because of # test: modules/import_in_config) if result.info.fileIndex == c.module.info.fileIndex and diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index eaa492a27..028dd00f0 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -35,6 +35,8 @@ import times, ropes, math, passes, ccgutils, wordrecg, renderer, rodread, rodutils, intsets, cgmeth, lowerings +from modulegraphs import ModuleGraph + type TTarget = enum targetJS, targetPHP @@ -2272,11 +2274,11 @@ proc myClose(b: PPassContext, n: PNode): PNode = for obj, content in items(globals.classes): genClass(obj, content, ext) -proc myOpenCached(s: PSym, rd: PRodReader): PPassContext = +proc myOpenCached(graph: ModuleGraph; s: PSym, rd: PRodReader): PPassContext = internalError("symbol files are not possible with the JS code generator") result = nil -proc myOpen(s: PSym; cache: IdentCache): PPassContext = +proc myOpen(graph: ModuleGraph; s: PSym; cache: IdentCache): PPassContext = var r = newModule(s) r.target = if gCmd == cmdCompileToPHP: targetPHP else: targetJS result = r diff --git a/compiler/main.nim b/compiler/main.nim index 103eef877..2118078be 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -15,7 +15,8 @@ import wordrecg, sem, semdata, idents, passes, docgen, extccomp, cgen, jsgen, json, nversion, platform, nimconf, importer, passaux, depends, vm, vmdef, types, idgen, - docgen2, service, parser, modules, ccgutils, sigmatch, ropes, lists + docgen2, service, parser, modules, ccgutils, sigmatch, ropes, lists, + modulegraphs from magicsys import systemModule, resetSysTypes @@ -30,80 +31,44 @@ proc semanticPasses = registerPass verbosePass registerPass semPass -proc commandGenDepend(cache: IdentCache) = +proc commandGenDepend(graph: ModuleGraph; cache: IdentCache) = semanticPasses() registerPass(gendependPass) registerPass(cleanupPass) - compileProject(cache) + compileProject(graph, cache) generateDot(gProjectFull) execExternalProgram("dot -Tpng -o" & changeFileExt(gProjectFull, "png") & ' ' & changeFileExt(gProjectFull, "dot")) -proc commandCheck(cache: IdentCache) = +proc commandCheck(graph: ModuleGraph; cache: IdentCache) = msgs.gErrorMax = high(int) # do not stop after first error defineSymbol("nimcheck") semanticPasses() # use an empty backend for semantic checking only rodPass() - compileProject(cache) + compileProject(graph, cache) -proc commandDoc2(cache: IdentCache; json: bool) = +proc commandDoc2(graph: ModuleGraph; cache: IdentCache; json: bool) = msgs.gErrorMax = high(int) # do not stop after first error semanticPasses() if json: registerPass(docgen2JsonPass) else: registerPass(docgen2Pass) #registerPass(cleanupPass()) - compileProject(cache) + compileProject(graph, cache) finishDoc2Pass(gProjectName) -proc commandCompileToC(cache: IdentCache) = +proc commandCompileToC(graph: ModuleGraph; cache: IdentCache) = extccomp.initVars() semanticPasses() registerPass(cgenPass) rodPass() #registerPass(cleanupPass()) - compileProject(cache) + compileProject(graph, cache) cgenWriteModules() if gCmd != cmdRun: extccomp.callCCompiler(changeFileExt(gProjectFull, "")) - if isServing: - # caas will keep track only of the compilation commands - lastCaasCmd = curCaasCmd - resetCgenModules() - for i in 0 .. <gMemCacheData.len: - gMemCacheData[i].hashStatus = hashCached - gMemCacheData[i].needsRecompile = Maybe - - # XXX: clean these global vars - # ccgstmts.gBreakpoints - # ccgthreadvars.nimtv - # ccgthreadvars.nimtVDeps - # ccgthreadvars.nimtvDeclared - # cgendata - # cgmeth? - # condsyms? - # depends? - # lexer.gLinesCompiled - # msgs - error counts - # magicsys, when system.nim changes - # rodread.rodcompilerProcs - # rodread.gTypeTable - # rodread.gMods - - # !! ropes.cache - # semthreads.computed? - # - # suggest.usageSym - # - # XXX: can we run out of IDs? - # XXX: detect config reloading (implement as error/require restart) - # XXX: options are appended (they will accumulate over time) - resetCompilationLists() - ccgutils.resetCaches() - GC_fullCollect() - -proc commandCompileToJS(cache: IdentCache) = +proc commandCompileToJS(graph: ModuleGraph; cache: IdentCache) = #incl(gGlobalOptions, optSafeCode) setTarget(osJS, cpuJS) #initDefines() @@ -113,9 +78,9 @@ proc commandCompileToJS(cache: IdentCache) = if gCmd == cmdCompileToPHP: defineSymbol("nimphp") semanticPasses() registerPass(JSgenPass) - compileProject(cache) + compileProject(graph, cache) -proc interactivePasses(cache: IdentCache) = +proc interactivePasses(graph: ModuleGraph; cache: IdentCache) = #incl(gGlobalOptions, optSafeCode) #setTarget(osNimrodVM, cpuNimrodVM) initDefines() @@ -125,28 +90,28 @@ proc interactivePasses(cache: IdentCache) = registerPass(semPass) registerPass(evalPass) -proc commandInteractive(cache: IdentCache) = +proc commandInteractive(graph: ModuleGraph; cache: IdentCache) = msgs.gErrorMax = high(int) # do not stop after first error - interactivePasses(cache) - compileSystemModule(cache) + interactivePasses(graph, cache) + compileSystemModule(graph, cache) if commandArgs.len > 0: - discard compileModule(fileInfoIdx(gProjectFull), cache, {}) + discard graph.compileModule(fileInfoIdx(gProjectFull), cache, {}) else: - var m = makeStdinModule() + var m = graph.makeStdinModule() incl(m.flags, sfMainModule) - processModule(m, llStreamOpenStdIn(), nil, cache) + processModule(graph, m, llStreamOpenStdIn(), nil, cache) const evalPasses = [verbosePass, semPass, evalPass] -proc evalNim(nodes: PNode, module: PSym; cache: IdentCache) = - carryPasses(nodes, module, cache, evalPasses) +proc evalNim(graph: ModuleGraph; nodes: PNode, module: PSym; cache: IdentCache) = + carryPasses(graph, nodes, module, cache, evalPasses) -proc commandEval(cache: IdentCache; exp: string) = +proc commandEval(graph: ModuleGraph; cache: IdentCache; exp: string) = if systemModule == nil: - interactivePasses(cache) - compileSystemModule(cache) + interactivePasses(graph, cache) + compileSystemModule(graph, cache) let echoExp = "echo \"eval\\t\", " & "repr(" & exp & ")" - evalNim(echoExp.parseString(cache), makeStdinModule(), cache) + evalNim(graph, echoExp.parseString(cache), makeStdinModule(graph), cache) proc commandScan(cache: IdentCache) = var f = addFileExt(mainCommandArg(), NimExt) @@ -165,75 +130,11 @@ proc commandScan(cache: IdentCache) = else: rawMessage(errCannotOpenFile, f) -proc commandSuggest(cache: IdentCache) = - if isServing: - # XXX: hacky work-around ahead - # Currently, it's possible to issue a idetools command, before - # issuing the first compile command. This will leave the compiler - # cache in a state where "no recompilation is necessary", but the - # cgen pass was never executed at all. - commandCompileToC(cache) - let gDirtyBufferIdx = gTrackPos.fileIndex - discard compileModule(gDirtyBufferIdx, cache, {sfDirty}) - resetModule(gDirtyBufferIdx) - else: - msgs.gErrorMax = high(int) # do not stop after first error - semanticPasses() - rodPass() - # XXX: this handles the case when the dirty buffer is the main file, - # but doesn't handle the case when it's imported module - #var projFile = if gProjectMainIdx == gDirtyOriginalIdx: gDirtyBufferIdx - # else: gProjectMainIdx - compileProject(cache) #(projFile) - -proc resetMemory = - resetCompilationLists() - ccgutils.resetCaches() - resetAllModules() - resetRopeCache() - resetSysTypes() - gOwners = @[] - resetIdentCache() - - # XXX: clean these global vars - # ccgstmts.gBreakpoints - # ccgthreadvars.nimtv - # ccgthreadvars.nimtVDeps - # ccgthreadvars.nimtvDeclared - # cgendata - # cgmeth? - # condsyms? - # depends? - # lexer.gLinesCompiled - # msgs - error counts - # magicsys, when system.nim changes - # rodread.rodcompilerProcs - # rodread.gTypeTable - # rodread.gMods - - # !! ropes.cache - # - # suggest.usageSym - # - # XXX: can we run out of IDs? - # XXX: detect config reloading (implement as error/require restart) - # XXX: options are appended (they will accumulate over time) - # vis = visimpl - when compileOption("gc", "v2"): - gcDebugging = true - echo "COLLECT 1" - GC_fullCollect() - echo "COLLECT 2" - GC_fullCollect() - echo "COLLECT 3" - GC_fullCollect() - echo GC_getStatistics() - const SimulateCaasMemReset = false PrintRopeCacheStats = false -proc mainCommand*(cache: IdentCache) = +proc mainCommand*(graph: ModuleGraph; cache: IdentCache) = when SimulateCaasMemReset: gGlobalOptions.incl(optCaasEnabled) @@ -249,28 +150,28 @@ proc mainCommand*(cache: IdentCache) = of "c", "cc", "compile", "compiletoc": # compile means compileToC currently gCmd = cmdCompileToC - commandCompileToC(cache) + commandCompileToC(graph, cache) of "cpp", "compiletocpp": gCmd = cmdCompileToCpp defineSymbol("cpp") - commandCompileToC(cache) + commandCompileToC(graph, cache) of "objc", "compiletooc": gCmd = cmdCompileToOC defineSymbol("objc") - commandCompileToC(cache) + commandCompileToC(graph, cache) of "run": gCmd = cmdRun when hasTinyCBackend: extccomp.setCC("tcc") - commandCompileToC(cache) + commandCompileToC(graph, cache) else: rawMessage(errInvalidCommandX, command) of "js", "compiletojs": gCmd = cmdCompileToJS - commandCompileToJS(cache) + commandCompileToJS(graph, cache) of "php": gCmd = cmdCompileToPHP - commandCompileToJS(cache) + commandCompileToJS(graph, cache) of "doc": wantMainModule() gCmd = cmdDoc @@ -280,7 +181,7 @@ proc mainCommand*(cache: IdentCache) = gCmd = cmdDoc loadConfigs(DocConfig, cache) defineSymbol("nimdoc") - commandDoc2(cache, false) + commandDoc2(graph, cache, false) of "rst2html": gCmd = cmdRst2html loadConfigs(DocConfig, cache) @@ -301,14 +202,14 @@ proc mainCommand*(cache: IdentCache) = loadConfigs(DocConfig, cache) wantMainModule() defineSymbol("nimdoc") - commandDoc2(cache, true) + commandDoc2(graph, cache, true) of "buildindex": gCmd = cmdDoc loadConfigs(DocConfig, cache) commandBuildIndex() of "gendepend": gCmd = cmdGenDepend - commandGenDepend(cache) + commandGenDepend(graph, cache) of "dump": gCmd = cmdDump if getConfigVar("dump.format") == "json": @@ -337,7 +238,7 @@ proc mainCommand*(cache: IdentCache) = for it in iterSearchPath(searchPaths): msgWriteln(it) of "check": gCmd = cmdCheck - commandCheck(cache) + commandCheck(graph, cache) of "parse": gCmd = cmdParse wantMainModule() @@ -346,26 +247,12 @@ proc mainCommand*(cache: IdentCache) = gCmd = cmdScan wantMainModule() commandScan(cache) - msgWriteln("Beware: Indentation tokens depend on the parser\'s state!") + msgWriteln("Beware: Indentation tokens depend on the parser's state!") of "secret": gCmd = cmdInteractive - commandInteractive(cache) + commandInteractive(graph, cache) of "e": - # XXX: temporary command for easier testing - commandEval(cache, mainCommandArg()) - of "reset": - resetMemory() - of "idetools": - gCmd = cmdIdeTools - if gEvalExpr != "": - commandEval(cache, gEvalExpr) - else: - commandSuggest(cache) - of "serve": - isServing = true - gGlobalOptions.incl(optCaasEnabled) - msgs.gErrorMax = high(int) # do not stop after first error - serve(cache, mainCommand) + commandEval(graph, cache, mainCommandArg()) of "nop", "help": # prevent the "success" message: gCmd = cmdDump @@ -393,4 +280,4 @@ proc mainCommand*(cache: IdentCache) = resetAttributes() -proc mainCommand*() = mainCommand(newIdentCache()) +proc mainCommand*() = mainCommand(newModuleGraph(), newIdentCache()) diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim new file mode 100644 index 000000000..9a3caa663 --- /dev/null +++ b/compiler/modulegraphs.nim @@ -0,0 +1,108 @@ +# +# +# The Nim Compiler +# (c) Copyright 2016 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements the module graph data structure. The module graph +## represents a complete Nim project. Single modules can either be kept in RAM +## or stored in a ROD file. The ROD file mechanism is not yet integrated here. +## +## The caching of modules is critical for 'nimsuggest' and is tricky to get +## right. If module E is being edited, we need autocompletion (and type +## checking) for E but we don't want to recompile depending +## modules right away for faster turnaround times. Instead we mark the module's +## dependencies as 'dirty'. Let D be a dependency of E. If D is dirty, we +## need to recompile it and all of its dependencies that are marked as 'dirty'. +## 'nimsuggest sug' actually is invoked for the file being edited so we know +## its content changed and there is no need to compute any checksums. +## Instead of a recursive algorithm, we use an iterative algorithm: +## +## - If a module gets recompiled, its dependencies need to be updated. +## - Its dependent module stays the same. +## + +import ast, intsets, tables + +type + ModuleGraph* = ref object + modules*: seq[PSym] ## indexed by int32 fileIdx + packageSyms*: TStrTable + deps*: IntSet # the dependency graph or potentially its transitive closure. + suggestMode*: bool # whether we are in nimsuggest mode or not. + invalidTransitiveClosure: bool + inclToMod*: Table[int32, int32] # mapping of include file to the + # first module that included it + +{.this: g.} + +proc newModuleGraph*(): ModuleGraph = + result = ModuleGraph() + initStrTable(result.packageSyms) + result.deps = initIntSet() + result.modules = @[] + result.inclToMod = initTable[int32, int32]() + +proc resetAllModules*(g: ModuleGraph) = + initStrTable(packageSyms) + deps = initIntSet() + modules = @[] + inclToMod = initTable[int32, int32]() + +proc getModule*(g: ModuleGraph; fileIdx: int32): PSym = + if fileIdx >= 0 and fileIdx < modules.len: + result = modules[fileIdx] + +proc dependsOn(a, b: int): int {.inline.} = (a shl 15) + b + +proc addDep*(g: ModuleGraph; m: PSym, dep: int32) = + if suggestMode: + deps.incl m.position.dependsOn(dep) + # we compute the transitive closure later when quering the graph lazily. + # this improve efficiency quite a lot: + invalidTransitiveClosure = true + +proc addIncludeDep*(g: ModuleGraph; module, includeFile: int32) = + discard hasKeyOrPut(inclToMod, includeFile, module) + +proc parentModule*(g: ModuleGraph; fileIdx: int32): int32 = + ## returns 'fileIdx' if the file belonging to this index is + ## directly used as a module or else the module that first + ## references this include file. + if fileIdx >= 0 and fileIdx < modules.len and modules[fileIdx] != nil: + result = fileIdx + else: + result = inclToMod.getOrDefault(fileIdx) + +proc transitiveClosure(g: var IntSet; n: int) = + # warshall's algorithm + for k in 0..<n: + for i in 0..<n: + for j in 0..<n: + if i != j and not g.contains(i.dependsOn(j)): + if g.contains(i.dependsOn(k)) and g.contains(k.dependsOn(j)): + g.incl i.dependsOn(j) + +proc markDirty*(g: ModuleGraph; fileIdx: int32) = + let m = getModule fileIdx + if m != nil: incl m.flags, sfDirty + +proc markClientsDirty*(g: ModuleGraph; fileIdx: int32) = + # we need to mark its dependent modules D as dirty right away because after + # nimsuggest is done with this module, the module's dirty flag will be + # cleared but D still needs to be remembered as 'dirty'. + if invalidTransitiveClosure: + invalidTransitiveClosure = false + transitiveClosure(deps, modules.len) + + # every module that *depends* on this file is also dirty: + for i in 0i32..<modules.len.int32: + let m = modules[i] + if m != nil and deps.contains(i.dependsOn(fileIdx)): + incl m.flags, sfDirty + +proc isDirty*(g: ModuleGraph; m: PSym): bool = + result = suggestMode and sfDirty in m.flags diff --git a/compiler/modules.nim b/compiler/modules.nim index be1de16d9..26ca2177b 100644 --- a/compiler/modules.nim +++ b/compiler/modules.nim @@ -7,130 +7,121 @@ # distribution, for details about the copyright. # -## implements the module handling +## Implements the module handling, including the caching of modules. import ast, astalgo, magicsys, securehash, rodread, msgs, cgendata, sigmatch, options, - idents, os, lexer, idgen, passes, syntaxes, llstream - -type - TNeedRecompile* = enum Maybe, No, Yes, Probing, Recompiled - THashStatus* = enum hashNotTaken, hashCached, hashHasChanged, hashNotChanged - - TModuleInMemory* = object - compiledAt*: float - hash*: SecureHash - deps*: seq[int32] ## XXX: slurped files are currently not tracked - needsRecompile*: TNeedRecompile - hashStatus*: THashStatus - -var - gCompiledModules: seq[PSym] = @[] - gMemCacheData*: seq[TModuleInMemory] = @[] - ## XXX: we should implement recycling of file IDs - ## if the user keeps renaming modules, the file IDs will keep growing - gFuzzyGraphChecking*: bool # nimsuggest uses this. XXX figure out why. - packageSyms: TStrTable - -initStrTable(packageSyms) - -proc getModule*(fileIdx: int32): PSym = - if fileIdx >= 0 and fileIdx < gCompiledModules.len: - result = gCompiledModules[fileIdx] - -proc hashChanged(fileIdx: int32): bool = - internalAssert fileIdx >= 0 and fileIdx < gMemCacheData.len - - template updateStatus = - gMemCacheData[fileIdx].hashStatus = if result: hashHasChanged - else: hashNotChanged - # echo "TESTING Hash: ", fileIdx.toFilename, " ", result - - case gMemCacheData[fileIdx].hashStatus - of hashHasChanged: - result = true - of hashNotChanged: - result = false - of hashCached: - let newHash = secureHashFile(fileIdx.toFullPath) - result = newHash != gMemCacheData[fileIdx].hash - gMemCacheData[fileIdx].hash = newHash - updateStatus() - of hashNotTaken: - gMemCacheData[fileIdx].hash = secureHashFile(fileIdx.toFullPath) - result = true - updateStatus() - -proc doHash(fileIdx: int32) = - if gMemCacheData[fileIdx].hashStatus == hashNotTaken: - # echo "FIRST Hash: ", fileIdx.ToFilename - gMemCacheData[fileIdx].hash = secureHashFile(fileIdx.toFullPath) - -proc addDep(x: PSym, dep: int32) = - growCache gMemCacheData, dep - gMemCacheData[x.position].deps.safeAdd(dep) - -proc resetModule*(fileIdx: int32) = - # echo "HARD RESETTING ", fileIdx.toFilename - if fileIdx <% gMemCacheData.len: - gMemCacheData[fileIdx].needsRecompile = Yes - if fileIdx <% gCompiledModules.len: - gCompiledModules[fileIdx] = nil - if fileIdx <% cgendata.gModules.len: - cgendata.gModules[fileIdx] = nil - -proc resetModule*(module: PSym) = - let conflict = getModule(module.position.int32) - if conflict == nil: return - doAssert conflict == module - resetModule(module.position.int32) - initStrTable(module.tab) - -proc resetAllModules* = - for i in 0..gCompiledModules.high: - if gCompiledModules[i] != nil: - resetModule(i.int32) - resetPackageCache() - initStrTable(packageSyms) - # for m in cgenModules(): echo "CGEN MODULE FOUND" - -proc resetAllModulesHard* = - resetPackageCache() - gCompiledModules.setLen 0 - gMemCacheData.setLen 0 - magicsys.resetSysTypes() - initStrTable(packageSyms) - # XXX - #gOwners = @[] - -proc checkDepMem(fileIdx: int32): TNeedRecompile = - template markDirty = - resetModule(fileIdx) - return Yes - - if gFuzzyGraphChecking: - if gMemCacheData[fileIdx].needsRecompile != Maybe: - return gMemCacheData[fileIdx].needsRecompile - else: - # cycle detection: We claim that a cycle does no harm. - if gMemCacheData[fileIdx].needsRecompile == Probing: - return No + idents, os, lexer, idgen, passes, syntaxes, llstream, modulegraphs + +when false: + type + TNeedRecompile* = enum Maybe, No, Yes, Probing, Recompiled + THashStatus* = enum hashNotTaken, hashCached, hashHasChanged, hashNotChanged + + TModuleInMemory* = object + hash*: SecureHash + deps*: seq[int32] ## XXX: slurped files are currently not tracked + + needsRecompile*: TNeedRecompile + hashStatus*: THashStatus + + var + gCompiledModules: seq[PSym] = @[] + gMemCacheData*: seq[TModuleInMemory] = @[] + ## XXX: we should implement recycling of file IDs + ## if the user keeps renaming modules, the file IDs will keep growing + gFuzzyGraphChecking*: bool # nimsuggest uses this. XXX figure out why. + + proc hashChanged(fileIdx: int32): bool = + internalAssert fileIdx >= 0 and fileIdx < gMemCacheData.len + + template updateStatus = + gMemCacheData[fileIdx].hashStatus = if result: hashHasChanged + else: hashNotChanged + # echo "TESTING Hash: ", fileIdx.toFilename, " ", result + + case gMemCacheData[fileIdx].hashStatus + of hashHasChanged: + result = true + of hashNotChanged: + result = false + of hashCached: + let newHash = secureHashFile(fileIdx.toFullPath) + result = newHash != gMemCacheData[fileIdx].hash + gMemCacheData[fileIdx].hash = newHash + updateStatus() + of hashNotTaken: + gMemCacheData[fileIdx].hash = secureHashFile(fileIdx.toFullPath) + result = true + updateStatus() + + proc doHash(fileIdx: int32) = + if gMemCacheData[fileIdx].hashStatus == hashNotTaken: + # echo "FIRST Hash: ", fileIdx.ToFilename + gMemCacheData[fileIdx].hash = secureHashFile(fileIdx.toFullPath) + + proc resetModule*(fileIdx: int32) = + # echo "HARD RESETTING ", fileIdx.toFilename + if fileIdx <% gMemCacheData.len: + gMemCacheData[fileIdx].needsRecompile = Yes + if fileIdx <% gCompiledModules.len: + gCompiledModules[fileIdx] = nil + if fileIdx <% cgendata.gModules.len: + cgendata.gModules[fileIdx] = nil + + proc resetModule*(module: PSym) = + let conflict = getModule(module.position.int32) + if conflict == nil: return + doAssert conflict == module + resetModule(module.position.int32) + initStrTable(module.tab) + + proc resetAllModules* = + for i in 0..gCompiledModules.high: + if gCompiledModules[i] != nil: + resetModule(i.int32) + resetPackageCache() + # for m in cgenModules(): echo "CGEN MODULE FOUND" + + proc resetAllModulesHard* = + resetPackageCache() + gCompiledModules.setLen 0 + gMemCacheData.setLen 0 + magicsys.resetSysTypes() + # XXX + #gOwners = @[] + + proc checkDepMem(fileIdx: int32): TNeedRecompile = + template markDirty = + resetModule(fileIdx) + return Yes + + if gFuzzyGraphChecking: + if gMemCacheData[fileIdx].needsRecompile != Maybe: + return gMemCacheData[fileIdx].needsRecompile + else: + # cycle detection: We claim that a cycle does no harm. + if gMemCacheData[fileIdx].needsRecompile == Probing: + return No - if optForceFullMake in gGlobalOptions or hashChanged(fileIdx): - markDirty() + if optForceFullMake in gGlobalOptions or hashChanged(fileIdx): + markDirty() - if gMemCacheData[fileIdx].deps != nil: - gMemCacheData[fileIdx].needsRecompile = Probing - for dep in gMemCacheData[fileIdx].deps: - let d = checkDepMem(dep) - if d in {Yes, Recompiled}: - # echo fileIdx.toFilename, " depends on ", dep.toFilename, " ", d - markDirty() + if gMemCacheData[fileIdx].deps != nil: + gMemCacheData[fileIdx].needsRecompile = Probing + for dep in gMemCacheData[fileIdx].deps: + let d = checkDepMem(dep) + if d in {Yes, Recompiled}: + # echo fileIdx.toFilename, " depends on ", dep.toFilename, " ", d + markDirty() - gMemCacheData[fileIdx].needsRecompile = No - return No + gMemCacheData[fileIdx].needsRecompile = No + return No + +proc resetSystemArtifacts*() = + magicsys.resetSysTypes() -proc newModule(fileIdx: int32): PSym = +proc newModule(graph: ModuleGraph; fileIdx: int32): PSym = # We cannot call ``newSym`` here, because we have to circumvent the ID # mechanism, which we do in order to assign each module a persistent ID. new(result) @@ -143,20 +134,19 @@ proc newModule(fileIdx: int32): PSym = result.info = newLineInfo(fileIdx, 1, 1) let pack = getIdent(getPackageName(filename)) - var packSym = packageSyms.strTableGet(pack) + var packSym = graph.packageSyms.strTableGet(pack) if packSym == nil: let pck = getPackageName(filename) let pck2 = if pck.len > 0: pck else: "unknown" packSym = newSym(skPackage, getIdent(pck2), nil, result.info) initStrTable(packSym.tab) - packageSyms.strTableAdd(packSym) + graph.packageSyms.strTableAdd(packSym) result.owner = packSym result.position = fileIdx - growCache gMemCacheData, fileIdx - growCache gCompiledModules, fileIdx - gCompiledModules[result.position] = result + growCache graph.modules, fileIdx + graph.modules[result.position] = result incl(result.flags, sfUsed) initStrTable(result.tab) @@ -167,13 +157,12 @@ proc newModule(fileIdx: int32): PSym = # strTableIncl() for error corrections: discard strTableIncl(packSym.tab, result) -proc compileModule*(fileIdx: int32; cache: IdentCache, flags: TSymFlags): PSym = - result = getModule(fileIdx) +proc compileModule*(graph: ModuleGraph; fileIdx: int32; cache: IdentCache, flags: TSymFlags): PSym = + result = graph.getModule(fileIdx) if result == nil: - growCache gMemCacheData, fileIdx - gMemCacheData[fileIdx].needsRecompile = Probing - result = newModule(fileIdx) - #var rd = handleSymbolFile(result) + #growCache gMemCacheData, fileIdx + #gMemCacheData[fileIdx].needsRecompile = Probing + result = newModule(graph, fileIdx) var rd: PRodReader result.flags = result.flags + flags if sfMainModule in result.flags: @@ -182,44 +171,52 @@ proc compileModule*(fileIdx: int32; cache: IdentCache, flags: TSymFlags): PSym = if gCmd in {cmdCompileToC, cmdCompileToCpp, cmdCheck, cmdIdeTools}: rd = handleSymbolFile(result, cache) if result.id < 0: - internalError("handleSymbolFile should have set the module\'s ID") + internalError("handleSymbolFile should have set the module's ID") return else: result.id = getID() - let validFile = processModule(result, + discard processModule(graph, result, if sfMainModule in flags and gProjectIsStdin: stdin.llStreamOpen else: nil, rd, cache) - if optCaasEnabled in gGlobalOptions: - gMemCacheData[fileIdx].compiledAt = gLastCmdTime - gMemCacheData[fileIdx].needsRecompile = Recompiled - if validFile: doHash fileIdx - else: - if checkDepMem(fileIdx) == Yes: - result = compileModule(fileIdx, cache, flags) - else: - result = gCompiledModules[fileIdx] - -proc importModule*(s: PSym, fileIdx: int32; cache: IdentCache): PSym {.procvar.} = + #if optCaasEnabled in gGlobalOptions: + # gMemCacheData[fileIdx].needsRecompile = Recompiled + # if validFile: doHash fileIdx + elif graph.isDirty(result): + result.flags.excl sfDirty + # reset module fields: + initStrTable(result.tab) + result.ast = nil + discard processModule(graph, result, + if sfMainModule in flags and gProjectIsStdin: stdin.llStreamOpen else: nil, + nil, cache) + graph.markClientsDirty(fileIdx) + when false: + if checkDepMem(fileIdx) == Yes: + result = compileModule(fileIdx, cache, flags) + else: + result = gCompiledModules[fileIdx] + +proc importModule*(graph: ModuleGraph; s: PSym, fileIdx: int32; + cache: IdentCache): PSym {.procvar.} = # this is called by the semantic checking phase - result = compileModule(fileIdx, cache, {}) - if optCaasEnabled in gGlobalOptions: addDep(s, fileIdx) + result = compileModule(graph, fileIdx, cache, {}) + graph.addDep(s, fileIdx) #if sfSystemModule in result.flags: # localError(result.info, errAttemptToRedefine, result.name.s) # restore the notes for outer module: gNotes = if s.owner.id == gMainPackageId: gMainPackageNotes else: ForeignPackageNotes -proc includeModule*(s: PSym, fileIdx: int32; cache: IdentCache): PNode {.procvar.} = +proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: int32; + cache: IdentCache): PNode {.procvar.} = result = syntaxes.parseFile(fileIdx, cache) - if optCaasEnabled in gGlobalOptions: - growCache gMemCacheData, fileIdx - addDep(s, fileIdx) - doHash(fileIdx) + graph.addDep(s, fileIdx) + graph.addIncludeDep(s.position.int32, fileIdx) -proc compileSystemModule*(cache: IdentCache) = +proc compileSystemModule*(graph: ModuleGraph; cache: IdentCache) = if magicsys.systemModule == nil: systemFileIdx = fileInfoIdx(options.libpath/"system.nim") - discard compileModule(systemFileIdx, cache, {sfSystemModule}) + discard graph.compileModule(systemFileIdx, cache, {sfSystemModule}) proc wantMainModule* = if gProjectFull.len == 0: @@ -229,18 +226,19 @@ proc wantMainModule* = passes.gIncludeFile = includeModule passes.gImportModule = importModule -proc compileProject*(cache: IdentCache; projectFileIdx = -1'i32) = +proc compileProject*(graph: ModuleGraph; cache: IdentCache; + projectFileIdx = -1'i32) = wantMainModule() let systemFileIdx = fileInfoIdx(options.libpath / "system.nim") let projectFile = if projectFileIdx < 0: gProjectMainIdx else: projectFileIdx if projectFile == systemFileIdx: - discard compileModule(projectFile, cache, {sfMainModule, sfSystemModule}) + discard graph.compileModule(projectFile, cache, {sfMainModule, sfSystemModule}) else: - compileSystemModule(cache) - discard compileModule(projectFile, cache, {sfMainModule}) + graph.compileSystemModule(cache) + discard graph.compileModule(projectFile, cache, {sfMainModule}) -proc makeModule*(filename: string): PSym = - result = newModule(fileInfoIdx filename) +proc makeModule*(graph: ModuleGraph; filename: string): PSym = + result = graph.newModule(fileInfoIdx filename) result.id = getID() -proc makeStdinModule*(): PSym = makeModule"stdin" +proc makeStdinModule*(graph: ModuleGraph): PSym = graph.makeModule"stdin" diff --git a/compiler/nim.nim b/compiler/nim.nim index aeab9421e..f8d6b607a 100644 --- a/compiler/nim.nim +++ b/compiler/nim.nim @@ -14,14 +14,14 @@ when defined(gcc) and defined(windows): {.link: "icons/nim_icon.o".} when defined(amd64) and defined(windows) and defined(vcc): - {.link: "icons/nim-amd64-windows-vcc.res" .} + {.link: "icons/nim-amd64-windows-vcc.res".} when defined(i386) and defined(windows) and defined(vcc): - {.link: "icons/nim-i386-windows-vcc.res" .} + {.link: "icons/nim-i386-windows-vcc.res".} import commands, lexer, condsyms, options, msgs, nversion, nimconf, ropes, extccomp, strutils, os, osproc, platform, main, parseopt, service, - nodejs, scriptconfig, idents + nodejs, scriptconfig, idents, modulegraphs when hasTinyCBackend: import tccgen @@ -73,7 +73,7 @@ proc handleCmdLine(cache: IdentCache) = processCmdLine(passCmd2, "") if options.command == "": rawMessage(errNoCommand, command) - mainCommand(cache) + mainCommand(newModuleGraph(), cache) if optHints in gOptions and hintGCStats in gNotes: echo(GC_getStatistics()) #echo(GC_getStatistics()) if msgs.gErrorCounter == 0: diff --git a/compiler/nimsuggest/nimsuggest.nim b/compiler/nimsuggest/nimsuggest.nim deleted file mode 100644 index 2be368d68..000000000 --- a/compiler/nimsuggest/nimsuggest.nim +++ /dev/null @@ -1,12 +0,0 @@ -# -# -# The Nim Compiler -# (c) Copyright 2015 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## Nimsuggest has been moved to https://github.com/nim-lang/nimsuggest - -{.error: "This project has moved to the following repo: https://github.com/nim-lang/nimsuggest".} diff --git a/compiler/passaux.nim b/compiler/passaux.nim index dc99ffd0c..eeaf12953 100644 --- a/compiler/passaux.nim +++ b/compiler/passaux.nim @@ -12,7 +12,9 @@ import strutils, ast, astalgo, passes, idents, msgs, options, idgen -proc verboseOpen(s: PSym; cache: IdentCache): PPassContext = +from modulegraphs import ModuleGraph + +proc verboseOpen(graph: ModuleGraph; s: PSym; cache: IdentCache): PPassContext = #MessageOut('compiling ' + s.name.s); result = nil # we don't need a context rawMessage(hintProcessing, s.name.s) diff --git a/compiler/passes.nim b/compiler/passes.nim index 9e383849a..4f1d4e3aa 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -13,7 +13,7 @@ import strutils, lists, options, ast, astalgo, llstream, msgs, platform, os, condsyms, idents, renderer, types, extccomp, math, magicsys, nversion, - nimsets, syntaxes, times, rodread, idgen + nimsets, syntaxes, times, rodread, idgen, modulegraphs type TPassContext* = object of RootObj # the pass's context @@ -21,9 +21,9 @@ type PPassContext* = ref TPassContext - TPassOpen* = proc (module: PSym; cache: IdentCache): PPassContext {.nimcall.} + TPassOpen* = proc (graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext {.nimcall.} TPassOpenCached* = - proc (module: PSym, rd: PRodReader): PPassContext {.nimcall.} + proc (graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext {.nimcall.} TPassClose* = proc (p: PPassContext, n: PNode): PNode {.nimcall.} TPassProcess* = proc (p: PPassContext, topLevelStmt: PNode): PNode {.nimcall.} @@ -48,8 +48,8 @@ proc makePass*(open: TPassOpen = nil, # the semantic checker needs these: var - gImportModule*: proc (m: PSym, fileIdx: int32; cache: IdentCache): PSym {.nimcall.} - gIncludeFile*: proc (m: PSym, fileIdx: int32; cache: IdentCache): PNode {.nimcall.} + gImportModule*: proc (graph: ModuleGraph; m: PSym, fileIdx: int32; cache: IdentCache): PSym {.nimcall.} + gIncludeFile*: proc (graph: ModuleGraph; m: PSym, fileIdx: int32; cache: IdentCache): PNode {.nimcall.} # implementation @@ -90,29 +90,32 @@ proc registerPass*(p: TPass) = gPasses[gPassesLen] = p inc(gPassesLen) -proc carryPass*(p: TPass, module: PSym; cache: IdentCache; +proc carryPass*(g: ModuleGraph; p: TPass, module: PSym; cache: IdentCache; m: TPassData): TPassData = - var c = p.open(module, cache) + var c = p.open(g, module, cache) result.input = p.process(c, m.input) result.closeOutput = if p.close != nil: p.close(c, m.closeOutput) else: m.closeOutput -proc carryPasses*(nodes: PNode, module: PSym; cache: IdentCache; passes: TPasses) = +proc carryPasses*(g: ModuleGraph; nodes: PNode, module: PSym; + cache: IdentCache; passes: TPasses) = var passdata: TPassData passdata.input = nodes for pass in passes: - passdata = carryPass(pass, module, cache, passdata) + passdata = carryPass(g, pass, module, cache, passdata) -proc openPasses(a: var TPassContextArray, module: PSym; cache: IdentCache) = +proc openPasses(g: ModuleGraph; a: var TPassContextArray; + module: PSym; cache: IdentCache) = for i in countup(0, gPassesLen - 1): if not isNil(gPasses[i].open): - a[i] = gPasses[i].open(module, cache) + a[i] = gPasses[i].open(g, module, cache) else: a[i] = nil -proc openPassesCached(a: var TPassContextArray, module: PSym, rd: PRodReader) = +proc openPassesCached(g: ModuleGraph; a: var TPassContextArray, module: PSym, + rd: PRodReader) = for i in countup(0, gPassesLen - 1): if not isNil(gPasses[i].openCached): - a[i] = gPasses[i].openCached(module, rd) + a[i] = gPasses[i].openCached(g, module, rd) if a[i] != nil: a[i].fromCache = true else: @@ -155,7 +158,7 @@ proc processImplicits(implicits: seq[string], nodeKind: TNodeKind, importStmt.addSon str if not processTopLevelStmt(importStmt, a): break -proc processModule*(module: PSym, stream: PLLStream, +proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream, rd: PRodReader; cache: IdentCache): bool {.discardable.} = var p: TParsers @@ -163,7 +166,7 @@ proc processModule*(module: PSym, stream: PLLStream, s: PLLStream fileIdx = module.fileIdx if rd == nil: - openPasses(a, module, cache) + openPasses(graph, a, module, cache) if stream == nil: let filename = fileIdx.toFullPathConsiderDirty s = llStreamOpen(filename, fmRead) @@ -203,7 +206,7 @@ proc processModule*(module: PSym, stream: PLLStream, # id synchronization point for more consistent code generation: idSynchronizationPoint(1000) else: - openPassesCached(a, module, rd) + openPassesCached(graph, a, module, rd) var n = loadInitSection(rd) for i in countup(0, sonsLen(n) - 1): processTopLevelStmtCached(n.sons[i], a) closePassesCached(a) diff --git a/compiler/rodwrite.nim b/compiler/rodwrite.nim index 4e8b27733..f2422f947 100644 --- a/compiler/rodwrite.nim +++ b/compiler/rodwrite.nim @@ -16,6 +16,8 @@ import condsyms, ropes, idents, securehash, rodread, passes, importer, idgen, rodutils +from modulegraphs import ModuleGraph + type TRodWriter = object of TPassContext module: PSym @@ -633,7 +635,7 @@ proc process(c: PPassContext, n: PNode): PNode = else: discard -proc myOpen(module: PSym; cache: IdentCache): PPassContext = +proc myOpen(g: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = if module.id < 0: internalError("rodwrite: module ID not set") var w = newRodWriter(module.fileIdx.getHash, module, cache) rawAddInterfaceSym(w, module) diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim index 891ee80ad..1105d3b67 100644 --- a/compiler/scriptconfig.nim +++ b/compiler/scriptconfig.nim @@ -13,7 +13,7 @@ import ast, modules, idents, passes, passaux, condsyms, options, nimconf, lists, sem, semdata, llstream, vm, vmdef, commands, msgs, - os, times, osproc, wordrecg, strtabs + os, times, osproc, wordrecg, strtabs, modulegraphs # we support 'cmpIgnoreStyle' natively for efficiency: from strutils import cmpIgnoreStyle, contains @@ -134,9 +134,11 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string): PEvalContext cbconf selfExe: setResult(a, os.getAppFilename()) -proc runNimScript*(cache: IdentCache; scriptName: string; freshDefines=true) = +proc runNimScript*(cache: IdentCache; scriptName: string; + freshDefines=true) = passes.gIncludeFile = includeModule passes.gImportModule = importModule + let graph = newModuleGraph() if freshDefines: initDefines() defineSymbol("nimscript") @@ -146,15 +148,15 @@ proc runNimScript*(cache: IdentCache; scriptName: string; freshDefines=true) = appendStr(searchPaths, options.libpath) - var m = makeModule(scriptName) + var m = graph.makeModule(scriptName) incl(m.flags, sfMainModule) vm.globalCtx = setupVM(m, cache, scriptName) - compileSystemModule(cache) - discard processModule(m, llStreamOpen(scriptName, fmRead), nil, cache) + graph.compileSystemModule(cache) + discard graph.processModule(m, llStreamOpen(scriptName, fmRead), nil, cache) # ensure we load 'system.nim' again for the real non-config stuff! - resetAllModulesHard() + resetSystemArtifacts() vm.globalCtx = nil # do not remove the defined symbols #initDefines() diff --git a/compiler/sem.nim b/compiler/sem.nim index e5ce7c6ce..02c779ef0 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -18,6 +18,8 @@ import evaltempl, patterns, parampatterns, sempass2, nimfix.pretty, semmacrosanity, semparallel, lowerings, pluginsupport, plugins.active +from modulegraphs import ModuleGraph + when defined(nimfix): import nimfix.prettybase @@ -398,8 +400,8 @@ proc addCodeForGenerics(c: PContext, n: PNode) = addSon(n, prc.ast) c.lastGenericIdx = c.generics.len -proc myOpen(module: PSym; cache: IdentCache): PPassContext = - var c = newContext(module, cache) +proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = + var c = newContext(graph, module, cache) if c.p != nil: internalError(module.info, "sem.myOpen") c.semConstExpr = semConstExpr c.semExpr = semExpr @@ -428,8 +430,8 @@ proc myOpen(module: PSym; cache: IdentCache): PPassContext = gNotes = ForeignPackageNotes result = c -proc myOpenCached(module: PSym; rd: PRodReader): PPassContext = - result = myOpen(module, rd.cache) +proc myOpenCached(graph: ModuleGraph; module: PSym; rd: PRodReader): PPassContext = + result = myOpen(graph, module, rd.cache) for m in items(rd.methods): methodDef(m, true) proc isImportSystemStmt(n: PNode): bool = diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 9a6011834..5b84b7cdf 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -1,7 +1,7 @@ # # # The Nim Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2016 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -13,7 +13,8 @@ import strutils, lists, intsets, options, lexer, ast, astalgo, trees, treetab, wordrecg, ropes, msgs, platform, os, condsyms, idents, renderer, types, extccomp, math, - magicsys, nversion, nimsets, parser, times, passes, rodread, vmdef + magicsys, nversion, nimsets, parser, times, passes, rodread, vmdef, + modulegraphs type TOptionEntry* = object of lists.TListEntry # entries to put on a @@ -107,6 +108,7 @@ type op: TTypeAttachedOp; col: int): PSym {.nimcall.} selfName*: PIdent cache*: IdentCache + graph*: ModuleGraph signatures*: TStrTable proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair = @@ -151,7 +153,7 @@ proc newOptionEntry*(): POptionEntry = result.dynlib = nil result.notes = gNotes -proc newContext*(module: PSym; cache: IdentCache): PContext = +proc newContext*(graph: ModuleGraph; module: PSym; cache: IdentCache): PContext = new(result) result.ambiguousSymbols = initIntSet() initLinkedList(result.optionStack) @@ -166,6 +168,7 @@ proc newContext*(module: PSym; cache: IdentCache): PContext = result.generics = @[] result.unknownIdents = initIntSet() result.cache = cache + result.graph = graph initStrTable(result.signatures) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index f8ec00b23..0c6f6848e 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -820,7 +820,7 @@ proc semAllTypeSections(c: PContext; n: PNode): PNode = if containsOrIncl(c.includedFiles, f): localError(n.info, errRecursiveDependencyX, f.toFilename) else: - let code = gIncludeFile(c.module, f, c.cache) + let code = gIncludeFile(c.graph, c.module, f, c.cache) gatherStmts c, code, result excl(c.includedFiles, f) of nkStmtList: @@ -1418,7 +1418,7 @@ proc evalInclude(c: PContext, n: PNode): PNode = if containsOrIncl(c.includedFiles, f): localError(n.info, errRecursiveDependencyX, f.toFilename) else: - addSon(result, semStmt(c, gIncludeFile(c.module, f, c.cache))) + addSon(result, semStmt(c, gIncludeFile(c.graph, c.module, f, c.cache))) excl(c.includedFiles, f) proc setLine(n: PNode, info: TLineInfo) = diff --git a/compiler/suggest.nim b/compiler/suggest.nim index 558581e04..39689099a 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -233,7 +233,7 @@ proc suggestFieldAccess(c: PContext, n: PNode, outputs: var int) = # error: no known module name: typ = nil else: - let m = gImportModule(c.module, fullpath.fileInfoIdx, c.cache) + let m = gImportModule(c.graph, c.module, fullpath.fileInfoIdx, c.cache) if m == nil: typ = nil else: for it in items(n.sym.tab): diff --git a/compiler/vm.nim b/compiler/vm.nim index 8e1c9f661..1bb440c6c 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -24,6 +24,8 @@ import from semfold import leValueConv, ordinalValToString from evaltempl import evalTemplate +from modulegraphs import ModuleGraph + when hasFFI: import evalffi @@ -1516,7 +1518,7 @@ proc setupGlobalCtx(module: PSym; cache: IdentCache) = else: refresh(globalCtx, module) -proc myOpen(module: PSym; cache: IdentCache): PPassContext = +proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = #var c = newEvalContext(module, emRepl) #c.features = {allowCast, allowFFI, allowInfiniteLoops} #pushStackFrame(c, newStackFrame()) diff --git a/doc/basicopt.txt b/doc/basicopt.txt index 9a1cfd956..eabd531a1 100644 --- a/doc/basicopt.txt +++ b/doc/basicopt.txt @@ -5,7 +5,7 @@ Command: //compile, c compile project with default code generator (C) //doc generate the documentation for inputfile - //doc2 generate the documentation for the whole project + //doc2 generate the documentation for inputfile Arguments: arguments are passed to the program being run (if --run option is selected) diff --git a/doc/intern.txt b/doc/intern.txt index 05847169f..d0aaa283a 100644 --- a/doc/intern.txt +++ b/doc/intern.txt @@ -459,7 +459,7 @@ This should produce roughly this code: PEnv = ref object x: int # data - proc anon(y: int, c: PClosure): int = + proc anon(y: int, c: PEnv): int = return y + c.x proc add(x: int): tuple[prc, data] = diff --git a/lib/pure/htmlparser.nim b/lib/pure/htmlparser.nim index 10a786555..00c622d74 100644 --- a/lib/pure/htmlparser.nim +++ b/lib/pure/htmlparser.nim @@ -494,12 +494,15 @@ proc untilElementEnd(x: var XmlParser, result: XmlNode, else: discard result.addNode(parse(x, errors)) of xmlElementEnd: - if cmpIgnoreCase(x.elemName, result.tag) == 0: - next(x) - else: + if cmpIgnoreCase(x.elemName, result.tag) != 0: #echo "5; expected: ", result.htmltag, " ", x.elemName adderr(expected(x, result)) - # do not skip it here! + # this seems to do better match error corrections in browsers: + while x.kind in {xmlElementEnd, xmlWhitespace}: + if x.kind == xmlElementEnd and cmpIgnoreCase(x.elemName, result.tag) == 0: + break + next(x) + next(x) break of xmlEof: adderr(expected(x, result)) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 8f8f36b80..f077e798a 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -613,6 +613,7 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1", if bytesread != bufSize: break dealloc(buf) close(s) + flushFile(d) close(d) proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 44ec5b548..76bd2dfe1 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -841,15 +841,19 @@ elif not defined(useNimRtl): if data.workingDir.len > 0: setCurrentDir($data.workingDir) var pid: Pid + var err: OSErrorCode if data.optionPoUsePath: res = posix_spawnp(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv) + if res != 0'i32: err = osLastError() else: res = posix_spawn(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv) + if res != 0'i32: err = osLastError() discard posix_spawn_file_actions_destroy(fops) discard posix_spawnattr_destroy(attr) - chck res + if res != 0'i32: raiseOSError(err) + return pid else: proc startProcessAuxFork(data: StartProcessData): Pid = diff --git a/lib/pure/parsexml.nim b/lib/pure/parsexml.nim index aa4a13ecf..978c9c516 100644 --- a/lib/pure/parsexml.nim +++ b/lib/pure/parsexml.nim @@ -572,6 +572,10 @@ proc parseAttribute(my: var XmlParser) = inc(pos) else: markError(my, errQuoteExpected) + # error corrections: guess what was meant + while buf[pos] != '>' and buf[pos] > ' ': + add(my.b, buf[pos]) + inc pos my.bufpos = pos parseWhitespace(my, skip=true) diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index bfc32bc71..129869373 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -179,9 +179,10 @@ proc isLowerAscii*(s: string): bool {.noSideEffect, procvar, if s.len() == 0: return false - result = true for c in s: - result = c.isLowerAscii() and result + if not c.isLowerAscii(): + return false + true proc isUpperAscii*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsUpperAsciiStr".}= @@ -193,9 +194,10 @@ proc isUpperAscii*(s: string): bool {.noSideEffect, procvar, if s.len() == 0: return false - result = true for c in s: - result = c.isUpperAscii() and result + if not c.isUpperAscii(): + return false + true proc toLowerAscii*(c: char): char {.noSideEffect, procvar, rtl, extern: "nsuToLowerAsciiChar".} = diff --git a/lib/pure/times.nim b/lib/pure/times.nim index db09f94c1..1e869d301 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -66,12 +66,6 @@ when defined(posix) and not defined(JS): when not defined(freebsd) and not defined(netbsd) and not defined(openbsd): var timezone {.importc, header: "<time.h>".}: int - var - tzname {.importc, header: "<time.h>" .}: array[0..1, cstring] - # we also need tzset() to make sure that tzname is initialized - proc tzset() {.importc, header: "<time.h>".} - # calling tzset() implicitly to initialize tzname data. - tzset() elif defined(windows): import winlean @@ -82,12 +76,10 @@ elif defined(windows): # visual c's c runtime exposes these under a different name var timezone {.importc: "_timezone", header: "<time.h>".}: int - tzname {.importc: "_tzname", header: "<time.h>"}: array[0..1, cstring] else: type TimeImpl {.importc: "time_t", header: "<time.h>".} = int var timezone {.importc, header: "<time.h>".}: int - tzname {.importc, header: "<time.h>" .}: array[0..1, cstring] type Time* = distinct TimeImpl @@ -104,7 +96,7 @@ elif defined(JS): getMinutes: proc (): int {.tags: [], raises: [], benign.} getMonth: proc (): int {.tags: [], raises: [], benign.} getSeconds: proc (): int {.tags: [], raises: [], benign.} - getTime: proc (): int {.tags: [], raises: [], benign.} + getTime: proc (): int {.tags: [], raises: [], noSideEffect, benign.} getTimezoneOffset: proc (): int {.tags: [], raises: [], benign.} getDate: proc (): int {.tags: [], raises: [], benign.} getUTCDate: proc (): int {.tags: [], raises: [], benign.} @@ -154,9 +146,11 @@ type ## Always 0 if the target is JS. isDST*: bool ## Determines whether DST is in effect. Always ## ``False`` if time is UTC. - tzname*: string ## The timezone this time is in. E.g. GMT timezone*: int ## The offset of the (non-DST) timezone in seconds - ## west of UTC. + ## west of UTC. Note that the sign of this number + ## is the opposite of the one in a formatted + ## timezone string like ``+01:00`` (which would be + ## parsed into the timezone ``-3600``). ## I make some assumptions about the data in here. Either ## everything should be positive or everything negative. Zero is @@ -184,7 +178,8 @@ proc getGMTime*(t: Time): TimeInfo {.tags: [TimeEffect], raises: [], benign.} ## converts the calendar time `t` to broken-down time representation, ## expressed in Coordinated Universal Time (UTC). -proc timeInfoToTime*(timeInfo: TimeInfo): Time {.tags: [], benign, deprecated.} +proc timeInfoToTime*(timeInfo: TimeInfo): Time + {.tags: [TimeEffect], benign, deprecated.} ## converts a broken-down time structure to ## calendar time representation. The function ignores the specified ## contents of the structure members `weekday` and `yearday` and recomputes @@ -193,7 +188,7 @@ proc timeInfoToTime*(timeInfo: TimeInfo): Time {.tags: [], benign, deprecated.} ## **Warning:** This procedure is deprecated since version 0.14.0. ## Use ``toTime`` instead. -proc toTime*(timeInfo: TimeInfo): Time {.tags: [], benign.} +proc toTime*(timeInfo: TimeInfo): Time {.tags: [TimeEffect], benign.} ## converts a broken-down time structure to ## calendar time representation. The function ignores the specified ## contents of the structure members `weekday` and `yearday` and recomputes @@ -211,36 +206,25 @@ proc fromSeconds*(since1970: int64): Time {.tags: [], raises: [], benign.} = proc toSeconds*(time: Time): float {.tags: [], raises: [], benign.} ## Returns the time in seconds since the unix epoch. -proc `$` *(timeInfo: TimeInfo): string {.tags: [], raises: [], benign.} - ## converts a `TimeInfo` object to a string representation. -proc `$` *(time: Time): string {.tags: [], raises: [], benign.} - ## converts a calendar time to a string representation. - proc `-`*(a, b: Time): int64 {. - rtl, extern: "ntDiffTime", tags: [], raises: [], benign.} + rtl, extern: "ntDiffTime", tags: [], raises: [], noSideEffect, benign.} ## computes the difference of two calendar times. Result is in seconds. proc `<`*(a, b: Time): bool {. - rtl, extern: "ntLtTime", tags: [], raises: [].} = + rtl, extern: "ntLtTime", tags: [], raises: [], noSideEffect.} = ## returns true iff ``a < b``, that is iff a happened before b. result = a - b < 0 proc `<=` * (a, b: Time): bool {. - rtl, extern: "ntLeTime", tags: [], raises: [].}= + rtl, extern: "ntLeTime", tags: [], raises: [], noSideEffect.}= ## returns true iff ``a <= b``. result = a - b <= 0 proc `==`*(a, b: Time): bool {. - rtl, extern: "ntEqTime", tags: [], raises: [].} = + rtl, extern: "ntEqTime", tags: [], raises: [], noSideEffect.} = ## returns true if ``a == b``, that is if both times represent the same value result = a - b == 0 -when not defined(JS): - proc getTzname*(): tuple[nonDST, DST: string] {.tags: [TimeEffect], raises: [], - benign.} - ## returns the local timezone; ``nonDST`` is the name of the local non-DST - ## timezone, ``DST`` is the name of the local DST timezone. - proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign.} ## returns the offset of the local (non-DST) timezone in seconds west of UTC. @@ -369,7 +353,7 @@ proc `+`*(a: TimeInfo, interval: TimeInterval): TimeInfo = ## very accurate. let t = toSeconds(toTime(a)) let secs = toSeconds(a, interval) - if a.tzname == "UTC": + if a.timezone == 0: result = getGMTime(fromSeconds(t + secs)) else: result = getLocalTime(fromSeconds(t + secs)) @@ -389,7 +373,7 @@ proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo = intval.months = - interval.months intval.years = - interval.years let secs = toSeconds(a, intval) - if a.tzname == "UTC": + if a.timezone == 0: result = getGMTime(fromSeconds(t + secs)) else: result = getLocalTime(fromSeconds(t + secs)) @@ -424,7 +408,8 @@ when not defined(JS): when not defined(JS): # C wrapper: - when defined(freebsd) or defined(netbsd) or defined(openbsd): + when defined(freebsd) or defined(netbsd) or defined(openbsd) or + defined(macosx): type StructTM {.importc: "struct tm", final.} = object second {.importc: "tm_sec".}, @@ -461,12 +446,6 @@ when not defined(JS): importc: "time", header: "<time.h>", tags: [].} proc mktime(t: StructTM): Time {. importc: "mktime", header: "<time.h>", tags: [].} - proc asctime(tblock: StructTM): cstring {. - importc: "asctime", header: "<time.h>", tags: [].} - proc ctime(time: ptr Time): cstring {. - importc: "ctime", header: "<time.h>", tags: [].} - # strftime(s: CString, maxsize: int, fmt: CString, t: tm): int {. - # importc: "strftime", header: "<time.h>".} proc getClock(): Clock {.importc: "clock", header: "<time.h>", tags: [TimeEffect].} proc difftime(a, b: Time): float {.importc: "difftime", header: "<time.h>", tags: [].} @@ -479,46 +458,17 @@ when not defined(JS): const weekDays: array[0..6, WeekDay] = [ dSun, dMon, dTue, dWed, dThu, dFri, dSat] - when defined(freebsd) or defined(netbsd) or defined(openbsd): - TimeInfo(second: int(tm.second), - minute: int(tm.minute), - hour: int(tm.hour), - monthday: int(tm.monthday), - month: Month(tm.month), - year: tm.year + 1900'i32, - weekday: weekDays[int(tm.weekday)], - yearday: int(tm.yearday), - isDST: tm.isdst > 0, - tzname: if local: - if tm.isdst > 0: - getTzname().DST - else: - getTzname().nonDST - else: - "UTC", - # BSD stores in `gmtoff` offset east of UTC in seconds, - # but posix systems using west of UTC in seconds - timezone: if local: -(tm.gmtoff) else: 0 - ) - else: - TimeInfo(second: int(tm.second), - minute: int(tm.minute), - hour: int(tm.hour), - monthday: int(tm.monthday), - month: Month(tm.month), - year: tm.year + 1900'i32, - weekday: weekDays[int(tm.weekday)], - yearday: int(tm.yearday), - isDST: tm.isdst > 0, - tzname: if local: - if tm.isdst > 0: - getTzname().DST - else: - getTzname().nonDST - else: - "UTC", - timezone: if local: getTimezone() else: 0 - ) + TimeInfo(second: int(tm.second), + minute: int(tm.minute), + hour: int(tm.hour), + monthday: int(tm.monthday), + month: Month(tm.month), + year: tm.year + 1900'i32, + weekday: weekDays[int(tm.weekday)], + yearday: int(tm.yearday), + isDST: tm.isdst > 0, + timezone: if local: getTimezone() else: 0 + ) proc timeInfoToTM(t: TimeInfo): StructTM = @@ -569,29 +519,18 @@ when not defined(JS): proc timeInfoToTime(timeInfo: TimeInfo): Time = var cTimeInfo = timeInfo # for C++ we have to make a copy, # because the header of mktime is broken in my version of libc - return mktime(timeInfoToTM(cTimeInfo)) + result = mktime(timeInfoToTM(cTimeInfo)) + # mktime is defined to interpret the input as local time. As timeInfoToTM + # does ignore the timezone, we need to adjust this here. + result = Time(TimeImpl(result) - getTimezone() + timeInfo.timezone) proc toTime(timeInfo: TimeInfo): Time = var cTimeInfo = timeInfo # for C++ we have to make a copy, # because the header of mktime is broken in my version of libc - return mktime(timeInfoToTM(cTimeInfo)) - - proc toStringTillNL(p: cstring): string = - result = "" - var i = 0 - while p[i] != '\0' and p[i] != '\10' and p[i] != '\13': - add(result, p[i]) - inc(i) - - proc `$`(timeInfo: TimeInfo): string = - # BUGFIX: asctime returns a newline at the end! - var p = asctime(timeInfoToTM(timeInfo)) - result = toStringTillNL(p) - - proc `$`(time: Time): string = - # BUGFIX: ctime returns a newline at the end! - var a = time - return toStringTillNL(ctime(addr(a))) + result = mktime(timeInfoToTM(cTimeInfo)) + # mktime is defined to interpret the input as local time. As timeInfoToTM + # does ignore the timezone, we need to adjust this here. + result = Time(TimeImpl(result) - getTimezone() + timeInfo.timezone) const epochDiff = 116444736000000000'i64 @@ -605,9 +544,6 @@ when not defined(JS): ## converts a Windows time to a UNIX `Time` (``time_t``) result = Time((t - epochDiff) div rateDiff) - proc getTzname(): tuple[nonDST, DST: string] = - return ($tzname[0], $tzname[1]) - proc getTimezone(): int = when defined(freebsd) or defined(netbsd) or defined(openbsd): var a = timec(nil) @@ -675,26 +611,16 @@ elif defined(JS): result.weekday = weekDays[t.getUTCDay()] result.yearday = 0 - proc timeInfoToTime*(timeInfo: TimeInfo): Time = - result = internGetTime() - result.setSeconds(timeInfo.second) - result.setMinutes(timeInfo.minute) - result.setHours(timeInfo.hour) - result.setMonth(ord(timeInfo.month)) - result.setFullYear(timeInfo.year) - result.setDate(timeInfo.monthday) + proc timeInfoToTime*(timeInfo: TimeInfo): Time = toTime(timeInfo) proc toTime*(timeInfo: TimeInfo): Time = result = internGetTime() - result.setSeconds(timeInfo.second) result.setMinutes(timeInfo.minute) result.setHours(timeInfo.hour) result.setMonth(ord(timeInfo.month)) result.setFullYear(timeInfo.year) result.setDate(timeInfo.monthday) - - proc `$`(timeInfo: TimeInfo): string = return $(toTime(timeInfo)) - proc `$`(time: Time): string = return $time.toLocaleString() + result.setSeconds(timeInfo.second + timeInfo.timezone) proc `-` (a, b: Time): int64 = return a.getTime() - b.getTime() @@ -802,6 +728,12 @@ proc `-`*(t: Time, ti: TimeInterval): Time = ## ``echo getTime() - 1.day`` result = toTime(getLocalTime(t) - ti) +const + secondsInMin = 60 + secondsInHour = 60*60 + secondsInDay = 60*60*24 + epochStartYear = 1970 + proc formatToken(info: TimeInfo, token: string, buf: var string) = ## Helper of the format proc to parse individual tokens. ## @@ -891,24 +823,28 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) = if fyear.len != 5: fyear = repeat('0', 5-fyear.len()) & fyear buf.add(fyear) of "z": - let hrs = (info.timezone div 60) div 60 - buf.add($hrs) + let hours = abs(info.timezone) div secondsInHour + if info.timezone < 0: buf.add('-') + else: buf.add('+') + buf.add($hours) of "zz": - let hrs = (info.timezone div 60) div 60 - - buf.add($hrs) - if hrs.abs < 10: - var atIndex = buf.len-(($hrs).len-(if hrs < 0: 1 else: 0)) - buf.insert("0", atIndex) + let hours = abs(info.timezone) div secondsInHour + if info.timezone < 0: buf.add('-') + else: buf.add('+') + if hours < 10: buf.add('0') + buf.add($hours) of "zzz": - let hrs = (info.timezone div 60) div 60 - - buf.add($hrs & ":00") - if hrs.abs < 10: - var atIndex = buf.len-(($hrs & ":00").len-(if hrs < 0: 1 else: 0)) - buf.insert("0", atIndex) - of "ZZZ": - buf.add(info.tzname) + let + hours = abs(info.timezone) div secondsInHour + minutes = abs(info.timezone) mod 60 + if info.timezone < 0: buf.add('-') + else: buf.add('+') + if hours < 10: buf.add('0') + buf.add($hours) + buf.add(':') + if minutes < 10: buf.add('0') + buf.add($minutes) + of "": discard else: @@ -945,8 +881,7 @@ proc format*(info: TimeInfo, f: string): string = ## yyyy Displays the year to four digits. ``2012 -> 2012`` ## z Displays the timezone offset from UTC. ``GMT+7 -> +7``, ``GMT-5 -> -5`` ## zz Same as above but with leading 0. ``GMT+7 -> +07``, ``GMT-5 -> -05`` - ## zzz Same as above but with ``:00``. ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00`` - ## ZZZ Displays the name of the timezone. ``GMT -> GMT``, ``EST -> EST`` + ## zzz Same as above but with ``:mm`` where *mm* represents minutes. ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00`` ## ========== ================================================================================= ================================================ ## ## Other strings can be inserted by putting them in ``''``. For example @@ -984,6 +919,18 @@ proc format*(info: TimeInfo, f: string): string = inc(i) +proc `$`*(timeInfo: TimeInfo): string {.tags: [], raises: [], benign.} = + ## converts a `TimeInfo` object to a string representation. + ## It uses the format ``yyyy-MM-dd'T'HH-mm-sszzz``. + try: + result = format(timeInfo, "yyyy-MM-dd'T'HH:mm:sszzz") # todo: optimize this + except ValueError: assert false # cannot happen because format string is valid + +proc `$`*(time: Time): string {.tags: [TimeEffect], raises: [], benign.} = + ## converts a `Time` value to a string representation. It will use the local + ## time zone and use the format ``yyyy-MM-dd'T'HH-mm-sszzz``. + $getLocalTime(time) + {.pop.} proc parseToken(info: var TimeInfo; token, value: string; j: var int) = @@ -1142,34 +1089,33 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = j += 4 of "z": if value[j] == '+': - info.timezone = parseInt($value[j+1]) + info.timezone = 0 - parseInt($value[j+1]) * secondsInHour elif value[j] == '-': - info.timezone = 0-parseInt($value[j+1]) + info.timezone = parseInt($value[j+1]) * secondsInHour else: raise newException(ValueError, "Couldn't parse timezone offset (z), got: " & value[j]) j += 2 of "zz": if value[j] == '+': - info.timezone = value[j+1..j+2].parseInt() + info.timezone = 0 - value[j+1..j+2].parseInt() * secondsInHour elif value[j] == '-': - info.timezone = 0-value[j+1..j+2].parseInt() + info.timezone = value[j+1..j+2].parseInt() * secondsInHour else: raise newException(ValueError, "Couldn't parse timezone offset (zz), got: " & value[j]) j += 3 of "zzz": - if value[j] == '+': - info.timezone = value[j+1..j+2].parseInt() - elif value[j] == '-': - info.timezone = 0-value[j+1..j+2].parseInt() + var factor = 0 + if value[j] == '+': factor = -1 + elif value[j] == '-': factor = 1 else: raise newException(ValueError, "Couldn't parse timezone offset (zzz), got: " & value[j]) - j += 6 - of "ZZZ": - info.tzname = value[j..j+2].toUpperAscii() - j += 3 + info.timezone = factor * value[j+1..j+2].parseInt() * secondsInHour + j += 4 + info.timezone += factor * value[j..j+1].parseInt() * 60 + j += 2 else: # Ignore the token and move forward in the value string by the same length j += token.len @@ -1203,8 +1149,7 @@ proc parse*(value, layout: string): TimeInfo = ## yyyy Displays the year to four digits. ``2012 -> 2012`` ## z Displays the timezone offset from UTC. ``GMT+7 -> +7``, ``GMT-5 -> -5`` ## zz Same as above but with leading 0. ``GMT+7 -> +07``, ``GMT-5 -> -05`` - ## zzz Same as above but with ``:00``. ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00`` - ## ZZZ Displays the name of the timezone. ``GMT -> GMT``, ``EST -> EST`` + ## zzz Same as above but with ``:mm`` where *mm* represents minutes. ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00`` ## ========== ================================================================================= ================================================ ## ## Other strings can be inserted by putting them in ``''``. For example @@ -1257,7 +1202,7 @@ proc parse*(value, layout: string): TimeInfo = let correctDST = getLocalTime(toTime(info)) info.isDST = correctDST.isDST - # Now we preocess it again with the correct isDST to correct things like + # Now we process it again with the correct isDST to correct things like # weekday and yearday. return getLocalTime(toTime(info)) @@ -1290,12 +1235,6 @@ proc countYearsAndDays*(daySpan: int): tuple[years: int, days: int] = result.years = days div 365 result.days = days mod 365 -const - secondsInMin = 60 - secondsInHour = 60*60 - secondsInDay = 60*60*24 - epochStartYear = 1970 - proc getDayOfWeek*(day, month, year: int): WeekDay = ## Returns the day of the week enum from day, month and year. # Day & month start from one. diff --git a/tests/stdlib/ttime.nim b/tests/stdlib/ttime.nim index 065009535..5d3c8325e 100644 --- a/tests/stdlib/ttime.nim +++ b/tests/stdlib/ttime.nim @@ -9,77 +9,93 @@ import # $ date --date='@2147483647' # Tue 19 Jan 03:14:07 GMT 2038 -var t = getGMTime(fromSeconds(2147483647)) -doAssert t.format("ddd dd MMM hh:mm:ss ZZZ yyyy") == "Tue 19 Jan 03:14:07 UTC 2038" -doAssert t.format("ddd ddMMMhh:mm:ssZZZyyyy") == "Tue 19Jan03:14:07UTC2038" - -doAssert t.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & - " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == - "19 19 Tue Tuesday 3 03 3 03 14 14 1 01 Jan January 7 07 A AM 8 38 038 2038 02038 0 00 00:00 UTC" - -doAssert t.format("yyyyMMddhhmmss") == "20380119031407" - -var t2 = getGMTime(fromSeconds(160070789)) # Mon 27 Jan 16:06:29 GMT 1975 -doAssert t2.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & - " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == - "27 27 Mon Monday 4 04 16 16 6 06 1 01 Jan January 29 29 P PM 5 75 975 1975 01975 0 00 00:00 UTC" +proc checkFormat(t: TimeInfo, format, expected: string) = + let actual = t.format(format) + if actual != expected: + echo "Formatting failure!" + echo "expected: ", expected + echo "actual : ", actual + doAssert false + +let t = getGMTime(fromSeconds(2147483647)) +t.checkFormat("ddd dd MMM hh:mm:ss yyyy", "Tue 19 Jan 03:14:07 2038") +t.checkFormat("ddd ddMMMhh:mm:ssyyyy", "Tue 19Jan03:14:072038") +t.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & + " ss t tt y yy yyy yyyy yyyyy z zz zzz", + "19 19 Tue Tuesday 3 03 3 03 14 14 1 01 Jan January 7 07 A AM 8 38 038 2038 02038 +0 +00 +00:00") + +t.checkFormat("yyyyMMddhhmmss", "20380119031407") + +let t2 = getGMTime(fromSeconds(160070789)) # Mon 27 Jan 16:06:29 GMT 1975 +t2.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & + " ss t tt y yy yyy yyyy yyyyy z zz zzz", + "27 27 Mon Monday 4 04 16 16 6 06 1 01 Jan January 29 29 P PM 5 75 975 1975 01975 +0 +00 +00:00") when not defined(JS): when sizeof(Time) == 8: var t3 = getGMTime(fromSeconds(889067643645)) # Fri 7 Jun 19:20:45 BST 30143 - doAssert t3.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & - " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == - "7 07 Fri Friday 6 06 18 18 20 20 6 06 Jun June 45 45 P PM 3 43 143 0143 30143 0 00 00:00 UTC" - doAssert t3.format(":,[]()-/") == ":,[]()-/" + t3.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & + " ss t tt y yy yyy yyyy yyyyy z zz zzz", + "7 07 Fri Friday 6 06 18 18 20 20 6 06 Jun June 45 45 P PM 3 43 143 0143 30143 +0 +00 +00:00") + t3.checkFormat(":,[]()-/", ":,[]()-/") var t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997 -doAssert t4.format("M MM MMM MMMM") == "10 10 Oct October" +t4.checkFormat("M MM MMM MMMM", "10 10 Oct October") # Interval tests -doAssert((t4 - initInterval(years = 2)).format("yyyy") == "1995") -doAssert((t4 - initInterval(years = 7, minutes = 34, seconds = 24)).format("yyyy mm ss") == "1990 24 10") +(t4 - initInterval(years = 2)).checkFormat("yyyy", "1995") +(t4 - initInterval(years = 7, minutes = 34, seconds = 24)).checkFormat("yyyy mm ss", "1990 24 10") proc parseTest(s, f, sExpected: string, ydExpected: int) = - let parsed = s.parse(f) - doAssert($parsed == sExpected) + let + parsed = s.parse(f) + parsedStr = $getGMTime(toTime(parsed)) + if parsedStr != sExpected: + echo "Parsing failure!" + echo "expected: ", sExpected + echo "actual : ", parsedStr + doAssert false doAssert(parsed.yearday == ydExpected) proc parseTestTimeOnly(s, f, sExpected: string) = doAssert(sExpected in $s.parse(f)) -parseTest("Tuesday at 09:04am on Dec 15, 2015", - "dddd at hh:mmtt on MMM d, yyyy", "Tue Dec 15 09:04:00 2015", 348) +# because setting a specific timezone for testing is platform-specific, we use +# explicit timezone offsets in all tests. + +parseTest("Tuesday at 09:04am on Dec 15, 2015 +0", + "dddd at hh:mmtt on MMM d, yyyy z", "2015-12-15T09:04:00+00:00", 348) # ANSIC = "Mon Jan _2 15:04:05 2006" -parseTest("Thu Jan 12 15:04:05 2006", "ddd MMM dd HH:mm:ss yyyy", - "Thu Jan 12 15:04:05 2006", 11) +parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z", + "2006-01-12T15:04:05+00:00", 11) # UnixDate = "Mon Jan _2 15:04:05 MST 2006" -parseTest("Thu Jan 12 15:04:05 MST 2006", "ddd MMM dd HH:mm:ss ZZZ yyyy", - "Thu Jan 12 15:04:05 2006", 11) +parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z", + "2006-01-12T15:04:05+00:00", 11) # RubyDate = "Mon Jan 02 15:04:05 -0700 2006" -parseTest("Mon Feb 29 15:04:05 -07:00 2016", "ddd MMM dd HH:mm:ss zzz yyyy", - "Mon Feb 29 15:04:05 2016", 59) # leap day +parseTest("Mon Feb 29 15:04:05 -07:00 2016 +0", "ddd MMM dd HH:mm:ss zzz yyyy z", + "2016-02-29T15:04:05+00:00", 59) # leap day # RFC822 = "02 Jan 06 15:04 MST" -parseTest("12 Jan 16 15:04 MST", "dd MMM yy HH:mm ZZZ", - "Tue Jan 12 15:04:00 2016", 11) +parseTest("12 Jan 16 15:04 +0", "dd MMM yy HH:mm z", + "2016-01-12T15:04:00+00:00", 11) # RFC822Z = "02 Jan 06 15:04 -0700" # RFC822 with numeric zone parseTest("01 Mar 16 15:04 -07:00", "dd MMM yy HH:mm zzz", - "Tue Mar 1 15:04:00 2016", 60) # day after february in leap year + "2016-03-01T22:04:00+00:00", 60) # day after february in leap year # RFC850 = "Monday, 02-Jan-06 15:04:05 MST" -parseTest("Monday, 12-Jan-06 15:04:05 MST", "dddd, dd-MMM-yy HH:mm:ss ZZZ", - "Thu Jan 12 15:04:05 2006", 11) +parseTest("Monday, 12-Jan-06 15:04:05 +0", "dddd, dd-MMM-yy HH:mm:ss z", + "2006-01-12T15:04:05+00:00", 11) # RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" -parseTest("Sun, 01 Mar 2015 15:04:05 MST", "ddd, dd MMM yyyy HH:mm:ss ZZZ", - "Sun Mar 1 15:04:05 2015", 59) # day after february in non-leap year +parseTest("Sun, 01 Mar 2015 15:04:05 +0", "ddd, dd MMM yyyy HH:mm:ss z", + "2015-03-01T15:04:05+00:00", 59) # day after february in non-leap year # RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" # RFC1123 with numeric zone parseTest("Thu, 12 Jan 2006 15:04:05 -07:00", "ddd, dd MMM yyyy HH:mm:ss zzz", - "Thu Jan 12 15:04:05 2006", 11) + "2006-01-12T22:04:05+00:00", 11) # RFC3339 = "2006-01-02T15:04:05Z07:00" parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-ddTHH:mm:ssZzzz", - "Thu Jan 12 15:04:05 2006", 11) + "2006-01-12T22:04:05+00:00", 11) parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-dd'T'HH:mm:ss'Z'zzz", - "Thu Jan 12 15:04:05 2006", 11) + "2006-01-12T22:04:05+00:00", 11) # RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" parseTest("2006-01-12T15:04:05.999999999Z-07:00", - "yyyy-MM-ddTHH:mm:ss.999999999Zzzz", "Thu Jan 12 15:04:05 2006", 11) + "yyyy-MM-ddTHH:mm:ss.999999999Zzzz", "2006-01-12T22:04:05+00:00", 11) # Kitchen = "3:04PM" parseTestTimeOnly("3:04PM", "h:mmtt", "15:04:00") #when not defined(testing): @@ -101,21 +117,20 @@ doAssert getDayOfWeekJulian(21, 9, 1970) == dMon doAssert getDayOfWeekJulian(1, 1, 2000) == dSat doAssert getDayOfWeekJulian(1, 1, 2021) == dFri -# toSeconds tests with GM and Local timezones -#var t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997 -var t4L = getLocalTime(fromSeconds(876124714)) -doAssert toSeconds(timeInfoToTime(t4L)) == 876124714 # fromSeconds is effectively "localTime" -doAssert toSeconds(timeInfoToTime(t4L)) + t4L.timezone.float == toSeconds(timeInfoToTime(t4)) +# toSeconds tests with GM timezone +let t4L = getGMTime(fromSeconds(876124714)) +doAssert toSeconds(toTime(t4L)) == 876124714 +doAssert toSeconds(toTime(t4L)) + t4L.timezone.float == toSeconds(toTime(t4)) # adding intervals var - a1L = toSeconds(timeInfoToTime(t4L + initInterval(hours = 1))) + t4L.timezone.float - a1G = toSeconds(timeInfoToTime(t4)) + 60.0 * 60.0 + a1L = toSeconds(toTime(t4L + initInterval(hours = 1))) + t4L.timezone.float + a1G = toSeconds(toTime(t4)) + 60.0 * 60.0 doAssert a1L == a1G # subtracting intervals -a1L = toSeconds(timeInfoToTime(t4L - initInterval(hours = 1))) + t4L.timezone.float -a1G = toSeconds(timeInfoToTime(t4)) - (60.0 * 60.0) +a1L = toSeconds(toTime(t4L - initInterval(hours = 1))) + t4L.timezone.float +a1G = toSeconds(toTime(t4)) - (60.0 * 60.0) doAssert a1L == a1G # add/subtract TimeIntervals and Time/TimeInfo @@ -164,3 +179,14 @@ let dstT1 = parse("2016-01-01 00:00:00", "yyyy-MM-dd HH:mm:ss") let dstT2 = parse("2016-06-01 00:00:00", "yyyy-MM-dd HH:mm:ss") doAssert dstT1 == getLocalTime(toTime(dstT1)) doAssert dstT2 == getLocalTime(toTime(dstT2)) + +# Comparison between Time objects should be detected by compiler +# as 'noSideEffect'. +proc cmpTimeNoSideEffect(t1: Time, t2: Time): bool {.noSideEffect.} = + result = t1 == t2 +doAssert cmpTimeNoSideEffect(0.fromSeconds, 0.fromSeconds) +# Additionally `==` generic for seq[T] has explicit 'noSideEffect' pragma +# so we can check above condition by comparing seq[Time] sequences +let seqA: seq[Time] = @[] +let seqB: seq[Time] = @[] +doAssert seqA == seqB diff --git a/tools/finish.nim b/tools/finish.nim index 8b9acfc56..cac001d79 100644 --- a/tools/finish.nim +++ b/tools/finish.nim @@ -114,8 +114,10 @@ proc main() = var mingWchoices: seq[string] = @[] var incompat: seq[string] = @[] for x in p.split(';'): - let y = expandFilename(if x[0] == '"' and x[^1] == '"': - substr(x, 1, x.len-2) else: x) + if x.len == 0: continue + let y = try: expandFilename(if x[0] == '"' and x[^1] == '"': + substr(x, 1, x.len-2) else: x) + except: "" if y == desiredPath: alreadyInPath = true if y.toLowerAscii.contains("mingw"): if dirExists(y): diff --git a/tools/nimsuggest/nimsuggest.nim b/tools/nimsuggest/nimsuggest.nim index c6a6bce05..822ef7224 100644 --- a/tools/nimsuggest/nimsuggest.nim +++ b/tools/nimsuggest/nimsuggest.nim @@ -17,7 +17,7 @@ import compiler/options, compiler/commands, compiler/modules, compiler/sem, compiler/passes, compiler/passaux, compiler/msgs, compiler/nimconf, compiler/extccomp, compiler/condsyms, compiler/lists, compiler/sigmatch, compiler/ast, compiler/scriptconfig, - compiler/idents + compiler/idents, compiler/modulegraphs when defined(windows): import winlean @@ -60,7 +60,7 @@ var const seps = {':', ';', ' ', '\t'} - Help = "usage: sug|con|def|use|dus|chk|highlight|outline file.nim[;dirtyfile.nim]:line:col\n" & + Help = "usage: sug|con|def|use|dus|chk|mod|highlight|outline file.nim[;dirtyfile.nim]:line:col\n" & "type 'quit' to quit\n" & "type 'debug' to toggle debug mode on/off\n" & "type 'terse' to toggle terse mode on/off" @@ -104,13 +104,13 @@ proc sexp(s: seq[Suggest]): SexpNode = for sug in s: result.add(sexp(sug)) -proc listEPC(): SexpNode = +proc listEpc(): SexpNode = # This function is called from Emacs to show available options. let argspecs = sexp("file line column dirtyfile".split(" ").map(newSSymbol)) docstring = sexp("line starts at 1, column at 0, dirtyfile is optional") result = newSList() - for command in ["sug", "con", "def", "use", "dus", "chk"]: + for command in ["sug", "con", "def", "use", "dus", "chk", "mod"]: let cmd = sexp(command) methodDesc = newSList() @@ -128,19 +128,19 @@ proc findNode(n: PNode): PSym = let res = n.sons[i].findNode if res != nil: return res -proc symFromInfo(gTrackPos: TLineInfo): PSym = - let m = getModule(gTrackPos.fileIndex) +proc symFromInfo(graph: ModuleGraph; gTrackPos: TLineInfo): PSym = + let m = graph.getModule(gTrackPos.fileIndex) #echo m.isNil, " I knew it ", gTrackPos.fileIndex if m != nil and m.ast != nil: result = m.ast.findNode proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int; - cache: IdentCache) = + graph: ModuleGraph; cache: IdentCache) = if gLogging: logStr("cmd: " & $cmd & ", file: " & file & ", dirtyFile: " & dirtyfile & "[" & $line & ":" & $col & "]") gIdeCmd = cmd if cmd == ideUse and suggestVersion != 2: - modules.resetAllModules() + graph.resetAllModules() var isKnownFile = true let dirtyIdx = file.fileInfoIdx(isKnownFile) @@ -152,23 +152,25 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int; if suggestVersion < 2: usageSym = nil if not isKnownFile: - compileProject(cache) + graph.compileProject(cache) if suggestVersion == 2 and gIdeCmd in {ideUse, ideDus} and dirtyfile.len == 0: discard "no need to recompile anything" else: - resetModule dirtyIdx - if dirtyIdx != gProjectMainIdx: - resetModule gProjectMainIdx - compileProject(cache, dirtyIdx) + let modIdx = graph.parentModule(dirtyIdx) + graph.markDirty dirtyIdx + graph.markClientsDirty dirtyIdx + if gIdeCmd != ideMod: + graph.compileProject(cache, modIdx) if gIdeCmd in {ideUse, ideDus}: - let u = if suggestVersion >= 2: symFromInfo(gTrackPos) else: usageSym + let u = if suggestVersion >= 2: graph.symFromInfo(gTrackPos) else: usageSym if u != nil: listUsages(u) else: localError(gTrackPos, "found no symbol at this position " & $gTrackPos) -proc executeEpc(cmd: IdeCmd, args: SexpNode; cache: IdentCache) = +proc executeEpc(cmd: IdeCmd, args: SexpNode; + graph: ModuleGraph; cache: IdentCache) = let file = args[0].getStr line = args[1].getNum @@ -176,7 +178,7 @@ proc executeEpc(cmd: IdeCmd, args: SexpNode; cache: IdentCache) = var dirtyfile = "" if len(args) > 3: dirtyfile = args[3].getStr(nil) - execute(cmd, file, dirtyfile, int(line), int(column), cache) + execute(cmd, file, dirtyfile, int(line), int(column), graph, cache) proc returnEpc(socket: var Socket, uid: BiggestInt, s: SexpNode|string, return_symbol = "return") = @@ -192,7 +194,7 @@ template sendEpc(results: typed, tdef, hook: untyped) = else: s ) - executeEpc(gIdeCmd, args, cache) + executeEpc(gIdeCmd, args, graph, cache) let res = sexp(results) if gLogging: logStr($res) @@ -215,7 +217,7 @@ proc connectToNextFreePort(server: Socket, host: string): Port = let (_, port) = server.getLocalAddr result = port -proc parseCmdLine(cmd: string; cache: IdentCache) = +proc parseCmdLine(cmd: string; graph: ModuleGraph; cache: IdentCache) = template toggle(sw) = if sw in gGlobalOptions: excl(gGlobalOptions, sw) @@ -235,6 +237,7 @@ proc parseCmdLine(cmd: string; cache: IdentCache) = of "def": gIdeCmd = ideDef of "use": gIdeCmd = ideUse of "dus": gIdeCmd = ideDus + of "mod": gIdeCmd = ideMod of "chk": gIdeCmd = ideChk incl(gGlobalOptions, optIdeDebug) @@ -256,25 +259,25 @@ proc parseCmdLine(cmd: string; cache: IdentCache) = i += skipWhile(cmd, seps, i) i += parseInt(cmd, col, i) - execute(gIdeCmd, orig, dirtyfile, line, col-1, cache) + execute(gIdeCmd, orig, dirtyfile, line, col-1, graph, cache) -proc serveStdin(cache: IdentCache) = +proc serveStdin(graph: ModuleGraph; cache: IdentCache) = if gEmitEof: echo DummyEof while true: let line = readLine(stdin) - parseCmdLine line, cache + parseCmdLine line, graph, cache echo DummyEof flushFile(stdout) else: echo Help var line = "" while readLineFromStdin("> ", line): - parseCmdLine line, cache + parseCmdLine line, graph, cache echo "" flushFile(stdout) -proc serveTcp(cache: IdentCache) = +proc serveTcp(graph: ModuleGraph; cache: IdentCache) = var server = newSocket() server.bindAddr(gPort, gAddress) var inp = "".TaintedString @@ -288,12 +291,12 @@ proc serveTcp(cache: IdentCache) = accept(server, stdoutSocket) stdoutSocket.readLine(inp) - parseCmdLine inp.string, cache + parseCmdLine inp.string, graph, cache stdoutSocket.send("\c\L") stdoutSocket.close() -proc serveEpc(server: Socket; cache: IdentCache) = +proc serveEpc(server: Socket; graph: ModuleGraph; cache: IdentCache) = var client = newSocket() # Wait for connection accept(server, client) @@ -346,11 +349,7 @@ proc serveEpc(server: Socket; cache: IdentCache) = "unexpected call: " & epcAPI raise newException(EUnexpectedCommand, errMessage) -template beCompatible() = - when compiles(modules.gFuzzyGraphChecking): - modules.gFuzzyGraphChecking = true - -proc mainCommand(cache: IdentCache) = +proc mainCommand(graph: ModuleGraph; cache: IdentCache) = clearPasses() registerPass verbosePass registerPass semPass @@ -368,26 +367,23 @@ proc mainCommand(cache: IdentCache) = case gMode of mstdin: - beCompatible() - compileProject(cache) + compileProject(graph, cache) #modules.gFuzzyGraphChecking = false - serveStdin(cache) + serveStdin(graph, cache) of mtcp: # until somebody accepted the connection, produce no output (logging is too # slow for big projects): msgs.writelnHook = proc (msg: string) = discard - beCompatible() - compileProject(cache) + compileProject(graph, cache) #modules.gFuzzyGraphChecking = false - serveTcp(cache) + serveTcp(graph, cache) of mepc: - beCompatible() var server = newSocket() let port = connectToNextFreePort(server, "localhost") server.listen() echo port - compileProject(cache) - serveEpc(server, cache) + compileProject(graph, cache) + serveEpc(server, graph, cache) proc processCmdLine*(pass: TCmdLinePass, cmd: string) = var p = parseopt.initOptParser(cmd) @@ -464,7 +460,9 @@ proc handleCmdLine(cache: IdentCache) = extccomp.initVars() processCmdLine(passCmd2, "") - mainCommand(cache) + let graph = newModuleGraph() + graph.suggestMode = true + mainCommand(graph, cache) when false: proc quitCalled() {.noconv.} = diff --git a/tools/nimsuggest/tester.nim b/tools/nimsuggest/tester.nim index 70a7da5b2..c90afe3db 100644 --- a/tools/nimsuggest/tester.nim +++ b/tools/nimsuggest/tester.nim @@ -7,27 +7,31 @@ import os, osproc, strutils, streams, re type Test = object - cmd: string + cmd, dest: string + startup: seq[string] script: seq[(string, string)] const - curDir = when defined(windows): "" else: "./" + curDir = when defined(windows): "" else: "" DummyEof = "!EOF!" +template tpath(): untyped = getAppDir() / "tests" + proc parseTest(filename: string): Test = const cursorMarker = "#[!]#" let nimsug = curDir & addFileExt("nimsuggest", ExeExt) - let dest = getTempDir() / extractFilename(filename) - result.cmd = nimsug & " --tester " & dest + result.dest = getTempDir() / extractFilename(filename) + result.cmd = nimsug & " --tester " & result.dest result.script = @[] - var tmp = open(dest, fmWrite) + result.startup = @[] + var tmp = open(result.dest, fmWrite) var specSection = 0 var markers = newSeq[string]() var i = 1 for x in lines(filename): let marker = x.find(cursorMarker)+1 if marker > 0: - markers.add filename & ";" & dest & ":" & $i & ":" & $marker + markers.add "\"" & filename & "\";\"" & result.dest & "\":" & $i & ":" & $marker tmp.writeLine x.replace(cursorMarker, "") else: tmp.writeLine x @@ -36,26 +40,104 @@ proc parseTest(filename: string): Test = elif specSection == 1: if x.startsWith("$nimsuggest"): result.cmd = x % ["nimsuggest", nimsug, "file", filename] + elif x.startsWith("!"): + if result.cmd.len == 0: + result.startup.add x + else: + result.script.add((x, "")) elif x.startsWith(">"): # since 'markers' here are not complete yet, we do the $substitutions # afterwards - result.script.add((x.substr(1), "")) - else: + result.script.add((x.substr(1).replaceWord("$path", tpath()), "")) + elif x.len > 0: # expected output line: let x = x % ["file", filename] result.script[^1][1].add x.replace(";;", "\t") & '\L' + # else: ignore empty lines for better readability of the specs inc i tmp.close() # now that we know the markers, substitute them: for a in mitems(result.script): a[0] = a[0] % markers +proc parseCmd(c: string): seq[string] = + # we don't support double quotes for now so that + # we can later support them properly with escapes and stuff. + result = @[] + var i = 0 + var a = "" + while true: + setLen(a, 0) + # eat all delimiting whitespace + while c[i] in {' ', '\t', '\l', '\r'}: inc(i) + case c[i] + of '"': raise newException(ValueError, "double quotes not yet supported: " & c) + of '\'': + var delim = c[i] + inc(i) # skip ' or " + while c[i] != '\0' and c[i] != delim: + add a, c[i] + inc(i) + if c[i] != '\0': inc(i) + of '\0': break + else: + while c[i] > ' ': + add(a, c[i]) + inc(i) + add(result, a) + +proc edit(tmpfile: string; x: seq[string]) = + if x.len != 3 and x.len != 4: + quit "!edit takes two or three arguments" + let f = if x.len >= 4: tpath() / x[3] else: tmpfile + try: + let content = readFile(f) + let newcontent = content.replace(x[1], x[2]) + if content == newcontent: + quit "wrong test case: edit had no effect" + writeFile(f, newcontent) + except IOError: + quit "cannot edit file " & tmpfile + +proc exec(x: seq[string]) = + if x.len != 2: quit "!exec takes one argument" + if execShellCmd(x[1]) != 0: + quit "External program failed " & x[1] + +proc copy(x: seq[string]) = + if x.len != 3: quit "!copy takes two arguments" + let rel = tpath() + copyFile(rel / x[1], rel / x[2]) + +proc del(x: seq[string]) = + if x.len != 2: quit "!del takes one argument" + removeFile(tpath() / x[1]) + +proc runCmd(cmd, dest: string): bool = + result = cmd[0] == '!' + if not result: return + let x = cmd.parseCmd() + case x[0] + of "!edit": + edit(dest, x) + of "!exec": + exec(x) + of "!copy": + copy(x) + of "!del": + del(x) + else: + quit "unkown command: " & cmd + proc smartCompare(pattern, x: string): bool = if pattern.contains('*'): result = match(x, re(escapeRe(pattern).replace("\\x2A","(.*)"), {})) proc runTest(filename: string): int = let s = parseTest filename + for cmd in s.startup: + if not runCmd(cmd, s.dest): + quit "invalid command: " & cmd let cl = parseCmdLine(s.cmd) var p = startProcess(command=cl[0], args=cl[1 .. ^1], options={poStdErrToStdOut, poUsePath, @@ -69,17 +151,18 @@ proc runTest(filename: string): int = while outp.readLine(a): if a == DummyEof: break for req, resp in items(s.script): - inp.writeLine(req) - inp.flush() - var answer = "" - while outp.readLine(a): - if a == DummyEof: break - answer.add a - answer.add '\L' - if resp != answer and not smartCompare(resp, answer): - report.add "\nTest failed: " & filename - report.add "\n Expected: " & resp - report.add "\n But got: " & answer + if not runCmd(req, s.dest): + inp.writeLine(req) + inp.flush() + var answer = "" + while outp.readLine(a): + if a == DummyEof: break + answer.add a + answer.add '\L' + if resp != answer and not smartCompare(resp, answer): + report.add "\nTest failed: " & filename + report.add "\n Expected: " & resp + report.add "\n But got: " & answer finally: inp.writeLine("quit") inp.flush() @@ -90,7 +173,7 @@ proc runTest(filename: string): int = proc main() = var failures = 0 - for x in walkFiles("tests/t*.nim"): + for x in walkFiles(getAppDir() / "tests/t*.nim"): echo "Test ", x failures += runTest(expandFilename(x)) if failures > 0: diff --git a/tools/nimsuggest/tests/dep_v1.nim b/tools/nimsuggest/tests/dep_v1.nim new file mode 100644 index 000000000..eae230e85 --- /dev/null +++ b/tools/nimsuggest/tests/dep_v1.nim @@ -0,0 +1,8 @@ + + + + + +type + Foo* = object + x*, y*: int diff --git a/tools/nimsuggest/tests/dep_v2.nim b/tools/nimsuggest/tests/dep_v2.nim new file mode 100644 index 000000000..ab39721c4 --- /dev/null +++ b/tools/nimsuggest/tests/dep_v2.nim @@ -0,0 +1,9 @@ + + + + + +type + Foo* = object + x*, y*: int + z*: string diff --git a/tools/nimsuggest/tests/tdot2.nim b/tools/nimsuggest/tests/tdot2.nim new file mode 100644 index 000000000..a58ac818b --- /dev/null +++ b/tools/nimsuggest/tests/tdot2.nim @@ -0,0 +1,29 @@ +# Test basic editing. We replace the 'false' by 'true' to +# see whether then the z field is suggested. + +const zField = 0i32 + +type + Foo = object + x, y: int + when zField == 1i32: + z: string + +proc main(f: Foo) = + f.#[!]# + +# the tester supports the spec section at the bottom of the file and +# this way, the line numbers more often stay the same +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skField;;x;;int;;$file;;8;;4;;"";;100 +sug;;skField;;y;;int;;$file;;8;;7;;"";;100 +sug;;skProc;;tdot2.main;;proc (f: Foo);;$file;;12;;5;;"";;100 +!edit 0i32 1i32 +>sug $1 +sug;;skField;;x;;int;;$file;;8;;4;;"";;100 +sug;;skField;;y;;int;;$file;;8;;7;;"";;100 +sug;;skField;;z;;string;;$file;;10;;6;;"";;100 +sug;;skProc;;tdot2.main;;proc (f: Foo);;$file;;12;;5;;"";;100 +""" diff --git a/tools/nimsuggest/tests/tdot3.nim b/tools/nimsuggest/tests/tdot3.nim new file mode 100644 index 000000000..5badde867 --- /dev/null +++ b/tools/nimsuggest/tests/tdot3.nim @@ -0,0 +1,27 @@ +# Test basic module dependency recompilations. + +import dep + +proc main(f: Foo) = + f.#[!]# + +# the tester supports the spec section at the bottom of the file and +# this way, the line numbers more often stay the same + +discard """ +!copy dep_v1.nim dep.nim +$nimsuggest --tester $file +>sug $1 +sug;;skField;;x;;int;;*dep.nim;;8;;4;;"";;100 +sug;;skField;;y;;int;;*dep.nim;;8;;8;;"";;100 +sug;;skProc;;tdot3.main;;proc (f: Foo);;$file;;5;;5;;"";;100 + +!copy dep_v2.nim dep.nim +>mod $path/dep.nim +>sug $1 +sug;;skField;;x;;int;;*dep.nim;;8;;4;;"";;100 +sug;;skField;;y;;int;;*dep.nim;;8;;8;;"";;100 +sug;;skField;;z;;string;;*dep.nim;;9;;4;;"";;100 +sug;;skProc;;tdot3.main;;proc (f: Foo);;$file;;5;;5;;"";;100 +!del dep.nim +""" diff --git a/tools/nimsuggest/tests/tinclude.nim b/tools/nimsuggest/tests/tinclude.nim new file mode 100644 index 000000000..77492d745 --- /dev/null +++ b/tools/nimsuggest/tests/tinclude.nim @@ -0,0 +1,7 @@ +discard """ +$nimsuggest --tester compiler/nim.nim +>def compiler/semexprs.nim:13:50 +def;;skType;;ast.PSym;;PSym;;*ast.nim;;668;;2;;"";;100 +>def compiler/semexprs.nim:13:50 +def;;skType;;ast.PSym;;PSym;;*ast.nim;;668;;2;;"";;100 +""" diff --git a/tools/nimsuggest/tests/tstrutils.nim b/tools/nimsuggest/tests/tstrutils.nim index 667c1660c..f5cda9505 100644 --- a/tools/nimsuggest/tests/tstrutils.nim +++ b/tools/nimsuggest/tests/tstrutils.nim @@ -1,6 +1,6 @@ discard """ -$nimsuggest --tester ../nim/lib/pure/strutils.nim ->def ../nim/lib/pure/strutils.nim:2300:6 +$nimsuggest --tester lib/pure/strutils.nim +>def lib/pure/strutils.nim:2300:6 def;;skTemplate;;system.doAssert;;proc (cond: bool, msg: string): typed;;*/lib/system.nim;;*;;9;;"";;100 """ diff --git a/web/news/e029_version_0_16_0.rst b/web/news/e029_version_0_16_0.rst index 2f6c72c82..94c9757a7 100644 --- a/web/news/e029_version_0_16_0.rst +++ b/web/news/e029_version_0_16_0.rst @@ -26,7 +26,9 @@ Changes affecting backwards compatibility - ``staticExec`` now uses the directory of the nim file that contains the ``staticExec`` call as the current working directory. - +- ``TimeInfo.tzname`` has been removed from ``times`` module because it was + broken. Because of this, the option ``"ZZZ"`` will no longer work in format + strings for formatting and parsing. Library Additions ----------------- |