diff options
Diffstat (limited to 'compiler/main.nim')
-rw-r--r-- | compiler/main.nim | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/compiler/main.nim b/compiler/main.nim new file mode 100644 index 000000000..4c52317cf --- /dev/null +++ b/compiler/main.nim @@ -0,0 +1,440 @@ +# +# +# The Nim Compiler +# (c) Copyright 2015 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# implements the command dispatcher and several commands + +when not defined(nimcore): + {.error: "nimcore MUST be defined for Nim's core tooling".} + +import + std/[strutils, os, times, tables, with, json], + llstream, ast, lexer, syntaxes, options, msgs, + condsyms, + idents, extccomp, + cgen, nversion, + platform, nimconf, depends, + modules, + modulegraphs, lineinfos, pathutils, vmprofiler + + +when defined(nimPreviewSlimSystem): + import std/[syncio, assertions] + +import ic / [cbackend, integrity, navigator, ic] + +import ../dist/checksums/src/checksums/sha1 + +import pipelines + +when not defined(leanCompiler): + import docgen + +proc writeDepsFile(g: ModuleGraph) = + let fname = g.config.nimcacheDir / RelativeFile(g.config.projectName & ".deps") + let f = open(fname.string, fmWrite) + for m in g.ifaces: + if m.module != nil: + f.writeLine(toFullPath(g.config, m.module.position.FileIndex)) + for k in g.inclToMod.keys: + if g.getModule(k).isNil: # don't repeat includes which are also modules + f.writeLine(toFullPath(g.config, k)) + f.close() + +proc writeCMakeDepsFile(conf: ConfigRef) = + ## write a list of C files for build systems like CMake. + ## only updated when the C file list changes. + let fname = getNimcacheDir(conf) / conf.outFile.changeFileExt("cdeps") + # generate output files list + var cfiles: seq[string] = @[] + for it in conf.toCompile: cfiles.add(it.cname.string) + let fileset = cfiles.toCountTable() + # read old cfiles list + var fl: File = default(File) + var prevset = initCountTable[string]() + if open(fl, fname.string, fmRead): + for line in fl.lines: prevset.inc(line) + fl.close() + # write cfiles out + if fileset != prevset: + fl = open(fname.string, fmWrite) + for line in cfiles: fl.writeLine(line) + fl.close() + +proc commandGenDepend(graph: ModuleGraph) = + setPipeLinePass(graph, GenDependPass) + compilePipelineProject(graph) + let project = graph.config.projectFull + writeDepsFile(graph) + generateDot(graph, project) + + # dot in graphivz tool kit is required + let graphvizDotPath = findExe("dot") + if graphvizDotPath.len == 0: + quit("gendepend: Graphviz's tool dot is required," & + "see https://graphviz.org/download for downloading") + + execExternalProgram(graph.config, "dot -Tpng -o" & + changeFileExt(project, "png").string & + ' ' & changeFileExt(project, "dot").string) + +proc commandCheck(graph: ModuleGraph) = + let conf = graph.config + conf.setErrorMaxHighMaybe + defineSymbol(conf.symbols, "nimcheck") + if optWasNimscript in conf.globalOptions: + defineSymbol(conf.symbols, "nimscript") + defineSymbol(conf.symbols, "nimconfig") + elif conf.backend == backendJs: + setTarget(conf.target, osJS, cpuJS) + setPipeLinePass(graph, SemPass) + compilePipelineProject(graph) + + if conf.symbolFiles != disabledSf: + case conf.ideCmd + of ideDef: navDefinition(graph) + of ideUse: navUsages(graph) + of ideDus: navDefusages(graph) + else: discard + writeRodFiles(graph) + +when not defined(leanCompiler): + proc commandDoc2(graph: ModuleGraph; ext: string) = + handleDocOutputOptions graph.config + graph.config.setErrorMaxHighMaybe + case ext: + of TexExt: + setPipeLinePass(graph, Docgen2TexPass) + of JsonExt: + setPipeLinePass(graph, Docgen2JsonPass) + of HtmlExt: + setPipeLinePass(graph, Docgen2Pass) + else: raiseAssert $ext + compilePipelineProject(graph) + +proc commandCompileToC(graph: ModuleGraph) = + let conf = graph.config + extccomp.initVars(conf) + if conf.symbolFiles == disabledSf: + if {optRun, optForceFullMake} * conf.globalOptions == {optRun} or isDefined(conf, "nimBetterRun"): + if not changeDetectedViaJsonBuildInstructions(conf, conf.jsonBuildInstructionsFile): + # nothing changed + graph.config.notes = graph.config.mainPackageNotes + return + + if not extccomp.ccHasSaneOverflow(conf): + conf.symbols.defineSymbol("nimEmulateOverflowChecks") + + if conf.symbolFiles == disabledSf: + setPipeLinePass(graph, CgenPass) + else: + setPipeLinePass(graph, SemPass) + compilePipelineProject(graph) + if graph.config.errorCounter > 0: + return # issue #9933 + if conf.symbolFiles == disabledSf: + cgenWriteModules(graph.backend, conf) + else: + if isDefined(conf, "nimIcIntegrityChecks"): + checkIntegrity(graph) + generateCode(graph) + # graph.backend can be nil under IC when nothing changed at all: + if graph.backend != nil: + cgenWriteModules(graph.backend, conf) + if conf.cmd != cmdTcc and graph.backend != nil: + extccomp.callCCompiler(conf) + # for now we do not support writing out a .json file with the build instructions when HCR is on + if not conf.hcrOn: + extccomp.writeJsonBuildInstructions(conf, graph.cachedFiles) + if optGenScript in graph.config.globalOptions: + writeDepsFile(graph) + if optGenCDeps in graph.config.globalOptions: + writeCMakeDepsFile(conf) + +proc commandJsonScript(graph: ModuleGraph) = + extccomp.runJsonBuildInstructions(graph.config, graph.config.jsonBuildInstructionsFile) + +proc commandCompileToJS(graph: ModuleGraph) = + let conf = graph.config + when defined(leanCompiler): + globalError(conf, unknownLineInfo, "compiler wasn't built with JS code generator") + else: + conf.exc = excCpp + setTarget(conf.target, osJS, cpuJS) + defineSymbol(conf.symbols, "ecmascript") # For backward compatibility + setPipeLinePass(graph, JSgenPass) + compilePipelineProject(graph) + if optGenScript in conf.globalOptions: + writeDepsFile(graph) + +proc commandInteractive(graph: ModuleGraph) = + graph.config.setErrorMaxHighMaybe + initDefines(graph.config.symbols) + defineSymbol(graph.config.symbols, "nimscript") + # note: seems redundant with -d:nimHasLibFFI + when hasFFI: defineSymbol(graph.config.symbols, "nimffi") + setPipeLinePass(graph, InterpreterPass) + compilePipelineSystemModule(graph) + if graph.config.commandArgs.len > 0: + discard graph.compilePipelineModule(fileInfoIdx(graph.config, graph.config.projectFull), {}) + else: + var m = graph.makeStdinModule() + incl(m.flags, sfMainModule) + var idgen = IdGenerator(module: m.itemId.module, symId: m.itemId.item, typeId: 0) + let s = llStreamOpenStdIn(onPrompt = proc() = flushDot(graph.config)) + discard processPipelineModule(graph, m, idgen, s) + +proc commandScan(cache: IdentCache, config: ConfigRef) = + var f = addFileExt(AbsoluteFile mainCommandArg(config), NimExt) + var stream = llStreamOpen(f, fmRead) + if stream != nil: + var + L: Lexer = default(Lexer) + tok: Token = default(Token) + openLexer(L, f, stream, cache, config) + while true: + rawGetTok(L, tok) + printTok(config, tok) + if tok.tokType == tkEof: break + closeLexer(L) + else: + rawMessage(config, errGenerated, "cannot open file: " & f.string) + +proc commandView(graph: ModuleGraph) = + let f = toAbsolute(mainCommandArg(graph.config), AbsoluteDir getCurrentDir()).addFileExt(RodExt) + rodViewer(f, graph.config, graph.cache) + +const + PrintRopeCacheStats = false + +proc hashMainCompilationParams*(conf: ConfigRef): string = + ## doesn't have to be complete; worst case is a cache hit and recompilation. + var state = newSha1State() + with state: + update os.getAppFilename() # nim compiler + update conf.commandLine # excludes `arguments`, as it should + update $conf.projectFull # so that running `nim r main` from 2 directories caches differently + result = $SecureHash(state.finalize()) + +proc setOutFile*(conf: ConfigRef) = + proc libNameTmpl(conf: ConfigRef): string {.inline.} = + result = if conf.target.targetOS == osWindows: "$1.lib" else: "lib$1.a" + + if conf.outFile.isEmpty: + var base = conf.projectName + if optUseNimcache in conf.globalOptions: + base.add "_" & hashMainCompilationParams(conf) + let targetName = + if conf.backend == backendJs: base & ".js" + elif optGenDynLib in conf.globalOptions: + platform.OS[conf.target.targetOS].dllFrmt % base + elif optGenStaticLib in conf.globalOptions: libNameTmpl(conf) % base + else: base & platform.OS[conf.target.targetOS].exeExt + conf.outFile = RelativeFile targetName + +proc mainCommand*(graph: ModuleGraph) = + let conf = graph.config + let cache = graph.cache + + conf.lastCmdTime = epochTime() + conf.searchPaths.add(conf.libpath) + + proc customizeForBackend(backend: TBackend) = + ## Sets backend specific options but don't compile to backend yet in + ## case command doesn't require it. This must be called by all commands. + if conf.backend == backendInvalid: + # only set if wasn't already set, to allow override via `nim c -b:cpp` + conf.backend = backend + + defineSymbol(graph.config.symbols, $conf.backend) + case conf.backend + of backendC: + if conf.exc == excNone: conf.exc = excSetjmp + of backendCpp: + if conf.exc == excNone: conf.exc = excCpp + of backendObjc: discard + of backendJs: + if conf.hcrOn: + # XXX: At the moment, system.nim cannot be compiled in JS mode + # with "-d:useNimRtl". The HCR option has been processed earlier + # and it has added this define implictly, so we must undo that here. + # A better solution might be to fix system.nim + undefSymbol(conf.symbols, "useNimRtl") + of backendInvalid: raiseAssert "unreachable" + + proc compileToBackend() = + customizeForBackend(conf.backend) + setOutFile(conf) + case conf.backend + of backendC: commandCompileToC(graph) + of backendCpp: commandCompileToC(graph) + of backendObjc: commandCompileToC(graph) + of backendJs: commandCompileToJS(graph) + of backendInvalid: raiseAssert "unreachable" + + template docLikeCmd(body) = + when defined(leanCompiler): + conf.quitOrRaise "compiler wasn't built with documentation generator" + else: + wantMainModule(conf) + let docConf = if conf.cmd == cmdDoc2tex: DocTexConfig else: DocConfig + loadConfigs(docConf, cache, conf, graph.idgen) + defineSymbol(conf.symbols, "nimdoc") + body + + ## command prepass + if conf.cmd == cmdCrun: conf.globalOptions.incl {optRun, optUseNimcache} + if conf.cmd notin cmdBackends + {cmdTcc}: customizeForBackend(backendC) + if conf.outDir.isEmpty: + # doc like commands can generate a lot of files (especially with --project) + # so by default should not end up in $PWD nor in $projectPath. + var ret = if optUseNimcache in conf.globalOptions: getNimcacheDir(conf) + else: conf.projectPath + if not ret.string.isAbsolute: # `AbsoluteDir` is not a real guarantee + rawMessage(conf, errCannotOpenFile, ret.string & "/") + if conf.cmd in cmdDocLike + {cmdRst2html, cmdRst2tex, cmdMd2html, cmdMd2tex}: + ret = ret / htmldocsDir + conf.outDir = ret + + ## process all commands + case conf.cmd + of cmdBackends: + compileToBackend() + when BenchIC: + echoTimes graph.packed + of cmdTcc: + when hasTinyCBackend: + extccomp.setCC(conf, "tcc", unknownLineInfo) + if conf.backend != backendC: + rawMessage(conf, errGenerated, "'run' requires c backend, got: '$1'" % $conf.backend) + compileToBackend() + else: + rawMessage(conf, errGenerated, "'run' command not available; rebuild with -d:tinyc") + of cmdDoc0: docLikeCmd commandDoc(cache, conf) + of cmdDoc: + docLikeCmd(): + conf.setNoteDefaults(warnRstRedefinitionOfLabel, false) # issue #13218 + # because currently generates lots of false positives due to conflation + # of labels links in doc comments, e.g. for random.rand: + # ## * `rand proc<#rand,Rand,Natural>`_ that returns an integer + # ## * `rand proc<#rand,Rand,range[]>`_ that returns a float + commandDoc2(graph, HtmlExt) + if optGenIndex in conf.globalOptions and optWholeProject in conf.globalOptions: + commandBuildIndex(conf, $conf.outDir) + of cmdRst2html, cmdMd2html: + # XXX: why are warnings disabled by default for rst2html and rst2tex? + for warn in rstWarnings: + conf.setNoteDefaults(warn, true) + conf.setNoteDefaults(warnRstRedefinitionOfLabel, false) # similar to issue #13218 + when defined(leanCompiler): + conf.quitOrRaise "compiler wasn't built with documentation generator" + else: + loadConfigs(DocConfig, cache, conf, graph.idgen) + commandRst2Html(cache, conf, preferMarkdown = (conf.cmd == cmdMd2html)) + of cmdRst2tex, cmdMd2tex, cmdDoc2tex: + for warn in rstWarnings: + conf.setNoteDefaults(warn, true) + when defined(leanCompiler): + conf.quitOrRaise "compiler wasn't built with documentation generator" + else: + if conf.cmd in {cmdRst2tex, cmdMd2tex}: + loadConfigs(DocTexConfig, cache, conf, graph.idgen) + commandRst2TeX(cache, conf, preferMarkdown = (conf.cmd == cmdMd2tex)) + else: + docLikeCmd commandDoc2(graph, TexExt) + of cmdJsondoc0: docLikeCmd commandJson(cache, conf) + of cmdJsondoc: + docLikeCmd(): + commandDoc2(graph, JsonExt) + if optGenIndex in conf.globalOptions and optWholeProject in conf.globalOptions: + commandBuildIndexJson(conf, $conf.outDir) + of cmdCtags: docLikeCmd commandTags(cache, conf) + of cmdBuildindex: docLikeCmd commandBuildIndex(conf, $conf.projectFull, conf.outFile) + of cmdGendepend: commandGenDepend(graph) + of cmdDump: + if getConfigVar(conf, "dump.format") == "json": + wantMainModule(conf) + + var definedSymbols = newJArray() + for s in definedSymbolNames(conf.symbols): definedSymbols.elems.add(%s) + + var libpaths = newJArray() + var lazyPaths = newJArray() + for dir in conf.searchPaths: libpaths.elems.add(%dir.string) + for dir in conf.lazyPaths: lazyPaths.elems.add(%dir.string) + + var hints = newJObject() # consider factoring with `listHints` + for a in hintMin..hintMax: + hints[$a] = %(a in conf.notes) + var warnings = newJObject() + for a in warnMin..warnMax: + warnings[$a] = %(a in conf.notes) + + var dumpdata = %[ + (key: "version", val: %VersionAsString), + (key: "nimExe", val: %(getAppFilename())), + (key: "prefixdir", val: %conf.getPrefixDir().string), + (key: "libpath", val: %conf.libpath.string), + (key: "project_path", val: %conf.projectFull.string), + (key: "defined_symbols", val: definedSymbols), + (key: "lib_paths", val: %libpaths), + (key: "lazyPaths", val: %lazyPaths), + (key: "outdir", val: %conf.outDir.string), + (key: "out", val: %conf.outFile.string), + (key: "nimcache", val: %getNimcacheDir(conf).string), + (key: "hints", val: hints), + (key: "warnings", val: warnings), + ] + + msgWriteln(conf, $dumpdata, {msgStdout, msgSkipHook, msgNoUnitSep}) + # `msgNoUnitSep` to avoid generating invalid json, refs bug #17853 + else: + msgWriteln(conf, "-- list of currently defined symbols --", + {msgStdout, msgSkipHook, msgNoUnitSep}) + for s in definedSymbolNames(conf.symbols): msgWriteln(conf, s, {msgStdout, msgSkipHook, msgNoUnitSep}) + msgWriteln(conf, "-- end of list --", {msgStdout, msgSkipHook}) + + for it in conf.searchPaths: msgWriteln(conf, it.string) + of cmdCheck: + commandCheck(graph) + of cmdM: + graph.config.symbolFiles = v2Sf + setUseIc(graph.config.symbolFiles != disabledSf) + commandCheck(graph) + of cmdParse: + wantMainModule(conf) + discard parseFile(conf.projectMainIdx, cache, conf) + of cmdRod: + wantMainModule(conf) + commandView(graph) + #msgWriteln(conf, "Beware: Indentation tokens depend on the parser's state!") + of cmdInteractive: commandInteractive(graph) + of cmdNimscript: + if conf.projectIsCmd or conf.projectIsStdin: discard + elif not fileExists(conf.projectFull): + rawMessage(conf, errGenerated, "NimScript file does not exist: " & conf.projectFull.string) + # main NimScript logic handled in `loadConfigs`. + of cmdNop: discard + of cmdJsonscript: + setOutFile(graph.config) + commandJsonScript(graph) + of cmdUnknown, cmdNone, cmdIdeTools: + rawMessage(conf, errGenerated, "invalid command: " & conf.command) + + if conf.errorCounter == 0 and conf.cmd notin {cmdTcc, cmdDump, cmdNop}: + if optProfileVM in conf.globalOptions: + echo conf.dump(conf.vmProfileData) + genSuccessX(conf) + + when PrintRopeCacheStats: + echo "rope cache stats: " + echo " tries : ", gCacheTries + echo " misses: ", gCacheMisses + echo " int tries: ", gCacheIntTries + echo " efficiency: ", formatFloat(1-(gCacheMisses.float/gCacheTries.float), + ffDecimal, 3) |