summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorTimothee Cour <timothee.cour2@gmail.com>2020-05-11 03:01:18 -0700
committerGitHub <noreply@github.com>2020-05-11 12:01:18 +0200
commit9502e39b634eea8e04f07ddc110b466387f42322 (patch)
tree240ffa98d6f1d556986dccbb66e9f5e0e81675e4
parentd11cb9d49596957e9fa097110cf19e9caf085592 (diff)
downloadNim-9502e39b634eea8e04f07ddc110b466387f42322.tar.gz
`nim doc --backend:js`, `nim doc --doccmd:-d:foo`, `nim r --backend:js`, `--doccmd:skip` + other improvements (#14278)
* `nim doc --backend:js|cpp...`
`nim doc --doccmd:'-d:foo --threads:on'`
`nim r --backend:cpp...` (implies --run --usenimcache)
* --usenimcache works with all targets
* --docCmd:skip now skips compiling snippets; 50X speedup for doc/manual.rst
-rw-r--r--compiler/ccgthreadvars.nim2
-rw-r--r--compiler/cgen.nim8
-rw-r--r--compiler/cmdlinehelper.nim3
-rw-r--r--compiler/commands.nim11
-rw-r--r--compiler/docgen.nim29
-rw-r--r--compiler/extccomp.nim24
-rw-r--r--compiler/lambdalifting.nim6
-rw-r--r--compiler/main.nim69
-rw-r--r--compiler/nim.nim12
-rw-r--r--compiler/options.nim32
-rw-r--r--compiler/pragmas.nim8
-rw-r--r--compiler/semexprs.nim2
-rw-r--r--compiler/sizealignoffsetimpl.nim2
-rw-r--r--compiler/transf.nim4
-rw-r--r--compiler/vmops.nim5
-rw-r--r--doc/advopt.txt11
-rw-r--r--doc/basicopt.txt6
-rw-r--r--lib/std/compilesettings.nim2
-rw-r--r--tests/nimdoc/m13129.nim36
-rw-r--r--tests/nimdoc/readme.md2
-rw-r--r--tests/trunner.nim37
-rw-r--r--tests/vm/tcompilesetting.nim1
22 files changed, 213 insertions, 99 deletions
diff --git a/compiler/ccgthreadvars.nim b/compiler/ccgthreadvars.nim
index 3701f337d..8bf5e573f 100644
--- a/compiler/ccgthreadvars.nim
+++ b/compiler/ccgthreadvars.nim
@@ -46,7 +46,7 @@ proc generateThreadLocalStorage(m: BModule) =
 
 proc generateThreadVarsSize(m: BModule) =
   if m.g.nimtv != nil:
-    let externc = if m.config.cmd == cmdCompileToCpp or
+    let externc = if m.config.backend == backendCpp or
                        sfCompileToCpp in m.module.flags: "extern \"C\" "
                   else: ""
     m.s[cfsProcs].addf(
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index 9e356206f..c32e86a88 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -279,7 +279,7 @@ proc genProc(m: BModule, prc: PSym)
 proc raiseInstr(p: BProc): Rope
 
 template compileToCpp(m: BModule): untyped =
-  m.config.cmd == cmdCompileToCpp or sfCompileToCpp in m.module.flags
+  m.config.backend == backendCpp or sfCompileToCpp in m.module.flags
 
 proc getTempName(m: BModule): Rope =
   result = m.tmpBase & rope(m.labels)
@@ -1066,11 +1066,11 @@ proc genProcAux(m: BModule, prc: PSym) =
 proc requiresExternC(m: BModule; sym: PSym): bool {.inline.} =
   result = (sfCompileToCpp in m.module.flags and
            sfCompileToCpp notin sym.getModule().flags and
-           m.config.cmd != cmdCompileToCpp) or (
+           m.config.backend != backendCpp) or (
            sym.flags * {sfInfixCall, sfCompilerProc, sfMangleCpp} == {} and
            sym.flags * {sfImportc, sfExportc} != {} and
            sym.magic == mNone and
-           m.config.cmd == cmdCompileToCpp)
+           m.config.backend == backendCpp)
 
 proc genProcPrototype(m: BModule, sym: PSym) =
   useHeader(m, sym)
@@ -1867,7 +1867,7 @@ proc writeHeader(m: BModule) =
 proc getCFile(m: BModule): AbsoluteFile =
   let ext =
       if m.compileToCpp: ".nim.cpp"
-      elif m.config.cmd == cmdCompileToOC or sfCompileToObjc in m.module.flags: ".nim.m"
+      elif m.config.backend == backendObjc or sfCompileToObjc in m.module.flags: ".nim.m"
       else: ".nim.c"
   result = changeFileExt(completeCfilePath(m.config, withPackageName(m.config, m.cfilename)), ext)
 
diff --git a/compiler/cmdlinehelper.nim b/compiler/cmdlinehelper.nim
index d5c743fc6..dd74f54dc 100644
--- a/compiler/cmdlinehelper.nim
+++ b/compiler/cmdlinehelper.nim
@@ -78,7 +78,8 @@ proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: Confi
   # XXX This is hacky. We need to find a better way.
   case conf.command
   of "cpp", "compiletocpp":
-    conf.cmd = cmdCompileToCpp
+    conf.backend = backendCpp
+    conf.cmd = cmdCompileToBackend
   else:
     discard
 
diff --git a/compiler/commands.nim b/compiler/commands.nim
index e980d64fc..2ab79f14a 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -434,11 +434,18 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
   of "outdir":
     expectArg(conf, switch, arg, pass, info)
     conf.outDir = processPath(conf, arg, info, notRelativeToProj=true)
+  of "usenimcache":
+    processOnOffSwitchG(conf, {optUseNimcache}, arg, pass, info)
   of "docseesrcurl":
     expectArg(conf, switch, arg, pass, info)
     conf.docSeeSrcUrl = arg
   of "docroot":
     conf.docRoot = if arg.len == 0: "@default" else: arg
+  of "backend", "b":
+    let backend = parseEnum(arg.normalize, TBackend.default)
+    if backend == TBackend.default: localError(conf, info, "invalid backend: '$1'" % arg)
+    conf.backend = backend
+  of "doccmd": conf.docCmd = arg
   of "mainmodule", "m":
     discard "allow for backwards compatibility, but don't do anything"
   of "define", "d":
@@ -495,7 +502,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
         defineSymbol(conf.symbols, "gcmarkandsweep")
       of "destructors", "arc":
         conf.selectedGC = gcArc
-        if conf.cmd != cmdCompileToCpp:
+        if conf.backend != backendCpp:
           conf.exc = excGoto
         defineSymbol(conf.symbols, "gcdestructors")
         defineSymbol(conf.symbols, "gcarc")
@@ -506,7 +513,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
           defineSymbol(conf.symbols, "nimV2")
       of "orc":
         conf.selectedGC = gcOrc
-        if conf.cmd != cmdCompileToCpp:
+        if conf.backend != backendCpp:
           conf.exc = excGoto
         defineSymbol(conf.symbols, "gcdestructors")
         defineSymbol(conf.symbols, "gcorc")
diff --git a/compiler/docgen.nim b/compiler/docgen.nim
index a42be9a9c..c01d5633f 100644
--- a/compiler/docgen.nim
+++ b/compiler/docgen.nim
@@ -22,6 +22,7 @@ import
 const
   exportSection = skField
   htmldocsDir = RelativeDir"htmldocs"
+  docCmdSkip = "skip"
 
 type
   TSections = array[TSymKind, Rope]
@@ -196,6 +197,7 @@ proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef,
   initStrTable result.types
   result.onTestSnippet =
     proc (gen: var RstGenerator; filename, cmd: string; status: int; content: string) =
+      if conf.docCmd == docCmdSkip: return
       inc(gen.id)
       var d = TDocumentor(gen)
       var outp: AbsoluteFile
@@ -444,21 +446,26 @@ proc testExample(d: PDoc; ex: PNode) =
   d.examples.add "import r\"" & outp.string & "\"\n"
 
 proc runAllExamples(d: PDoc) =
-  if d.examples.len == 0: return
+  let docCmd = d.conf.docCmd
+  let backend = d.conf.backend
+  # This used to be: `let backend = if isDefined(d.conf, "js"): "js"` (etc), however
+  # using `-d:js` (etc) cannot work properly, eg would fail with `importjs`
+  # since semantics are affected by `config.backend`, not by isDefined(d.conf, "js")
+  if d.examples.len == 0 or docCmd == docCmdSkip: return
   let outputDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples"
   let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" &
       "_examples.nim"))
   writeFile(outp, d.examples)
-  let backend = if isDefined(d.conf, "js"): "js"
-                elif isDefined(d.conf, "cpp"): "cpp"
-                elif isDefined(d.conf, "objc"): "objc"
-                else: "c"
-  if os.execShellCmd(os.getAppFilename() & " " & backend &
-                    " --warning[UnusedImport]:off" &
-                    " --path:" & quoteShell(d.conf.projectPath) &
-                    " --nimcache:" & quoteShell(outputDir) &
-                    " -r " & quoteShell(outp)) != 0:
-    quit "[Examples] failed: see " & outp.string
+  let cmd = "$nim $backend -r --warning:UnusedImport:off --path:$path --nimcache:$nimcache $docCmd $file" % [
+    "nim", os.getAppFilename(),
+    "backend", $d.conf.backend,
+    "path", quoteShell(d.conf.projectPath),
+    "nimcache", quoteShell(outputDir),
+    "file", quoteShell(outp),
+    "docCmd", docCmd,
+  ]
+  if os.execShellCmd(cmd) != 0:
+    quit "[runnableExamples] failed: generated file: '$1' cmd: $2" % [outp.string, cmd]
   else:
     # keep generated source file `outp` to allow inspection.
     rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp.string])
diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim
index 7c255484d..3c2435bec 100644
--- a/compiler/extccomp.nim
+++ b/compiler/extccomp.nim
@@ -307,14 +307,12 @@ proc getConfigVar(conf: ConfigRef; c: TSystemCC, suffix: string): string =
   # use ``cpu.os.cc`` for cross compilation, unless ``--compileOnly`` is given
   # for niminst support
   let fullSuffix =
-    if conf.cmd == cmdCompileToCpp:
-      ".cpp" & suffix
-    elif conf.cmd == cmdCompileToOC:
-      ".objc" & suffix
-    elif conf.cmd == cmdCompileToJS:
-      ".js" & suffix
+    case conf.backend
+    of backendCpp, backendJs, backendObjc: "." & $conf.backend & suffix
+    of backendC: suffix
     else:
-      suffix
+      doAssert false
+      ""
 
   if (conf.target.hostOS != conf.target.targetOS or conf.target.hostCPU != conf.target.targetCPU) and
       optCompileOnly notin conf.globalOptions:
@@ -481,10 +479,10 @@ proc needsExeExt(conf: ConfigRef): bool {.inline.} =
            (conf.target.hostOS == osWindows)
 
 proc useCpp(conf: ConfigRef; cfile: AbsoluteFile): bool =
-  conf.cmd == cmdCompileToCpp and not cfile.string.endsWith(".c")
+  conf.backend == backendCpp and not cfile.string.endsWith(".c")
 
 proc envFlags(conf: ConfigRef): string =
-  result = if conf.cmd == cmdCompileToCpp:
+  result = if conf.backend == backendCpp:
             getEnv("CXXFLAGS")
           else:
             getEnv("CFLAGS")
@@ -535,7 +533,7 @@ proc ccHasSaneOverflow*(conf: ConfigRef): bool =
 
 proc getLinkerExe(conf: ConfigRef; compiler: TSystemCC): string =
   result = if CC[compiler].linkerExe.len > 0: CC[compiler].linkerExe
-           elif optMixedMode in conf.globalOptions and conf.cmd != cmdCompileToCpp: CC[compiler].cppCompiler
+           elif optMixedMode in conf.globalOptions and conf.backend != backendCpp: CC[compiler].cppCompiler
            else: getCompilerExe(conf, compiler, AbsoluteFile"")
 
 proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile,
@@ -626,8 +624,10 @@ proc footprint(conf: ConfigRef; cfile: Cfile): SecureHash =
     getCompileCFileCmd(conf, cfile))
 
 proc externalFileChanged(conf: ConfigRef; cfile: Cfile): bool =
-  if conf.cmd notin {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC, cmdCompileToLLVM, cmdNone}:
-    return false
+  case conf.backend
+  of backendInvalid: doAssert false
+  of backendJs: return false # pre-existing behavior, but not sure it's good
+  else: discard
 
   var hashFile = toGeneratedFile(conf, conf.withPackageName(cfile.cname), "sha1")
   var currentHash = footprint(conf, cfile)
diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim
index 01def12ad..fc1ad5e40 100644
--- a/compiler/lambdalifting.nim
+++ b/compiler/lambdalifting.nim
@@ -235,7 +235,7 @@ template isIterator*(owner: PSym): bool =
 proc liftingHarmful(conf: ConfigRef; owner: PSym): bool {.inline.} =
   ## lambda lifting can be harmful for JS-like code generators.
   let isCompileTime = sfCompileTime in owner.flags or owner.kind == skMacro
-  result = conf.cmd == cmdCompileToJS and not isCompileTime
+  result = conf.backend == backendJs and not isCompileTime
 
 proc createTypeBoundOpsLL(g: ModuleGraph; refType: PType; info: TLineInfo; owner: PSym) =
   createTypeBoundOps(g, nil, refType.lastSon, info)
@@ -846,14 +846,14 @@ proc liftIterToProc*(g: ModuleGraph; fn: PSym; body: PNode; ptrType: PType): PNo
   fn.typ.callConv = oldCC
 
 proc liftLambdas*(g: ModuleGraph; fn: PSym, body: PNode; tooEarly: var bool): PNode =
-  # XXX gCmd == cmdCompileToJS does not suffice! The compiletime stuff needs
+  # XXX backend == backendJs does not suffice! The compiletime stuff needs
   # the transformation even when compiling to JS ...
 
   # However we can do lifting for the stuff which is *only* compiletime.
   let isCompileTime = sfCompileTime in fn.flags or fn.kind == skMacro
 
   if body.kind == nkEmpty or (
-      g.config.cmd == cmdCompileToJS and not isCompileTime) or
+      g.config.backend == backendJs and not isCompileTime) or
       fn.skipGenericOwner.kind != skModule:
 
     # ignore forward declaration:
diff --git a/compiler/main.nim b/compiler/main.nim
index c94af24d5..00bc579df 100644
--- a/compiler/main.nim
+++ b/compiler/main.nim
@@ -119,8 +119,7 @@ when not defined(leanCompiler):
     let conf = graph.config
     conf.exc = excCpp
 
-    if conf.outDir.isEmpty:
-      conf.outDir = conf.projectPath
+    setOutDir(conf)
     if conf.outFile.isEmpty:
       conf.outFile = RelativeFile(conf.projectName & ".js")
 
@@ -128,7 +127,6 @@ when not defined(leanCompiler):
     setTarget(graph.config.target, osJS, cpuJS)
     #initDefines()
     defineSymbol(graph.config.symbols, "ecmascript") # For backward compatibility
-    defineSymbol(graph.config.symbols, "js")
     semanticPasses(graph)
     registerPass(graph, JSgenPass)
     compileProject(graph)
@@ -190,25 +188,46 @@ proc mainCommand*(graph: ModuleGraph) =
   conf.lastCmdTime = epochTime()
   conf.searchPaths.add(conf.libpath)
   setId(100)
-  template handleC() =
-    conf.cmd = cmdCompileToC
-    if conf.exc == excNone: conf.exc = excSetjmp
-    defineSymbol(graph.config.symbols, "c")
-    commandCompileToC(graph)
+
+  ## Calling `setOutDir(conf)` unconditionally would fix regression
+  ## https://github.com/nim-lang/Nim/issues/6583#issuecomment-625711125
+  when false: setOutDir(conf)
+  if optUseNimcache in conf.globalOptions: setOutDir(conf)
+
+  template handleBackend(backend2: TBackend) =
+    conf.backend = backend2
+    conf.cmd = cmdCompileToBackend
+    defineSymbol(graph.config.symbols, $backend2)
+    case backend2
+    of backendC:
+      if conf.exc == excNone: conf.exc = excSetjmp
+      commandCompileToC(graph)
+    of backendCpp:
+      if conf.exc == excNone: conf.exc = excCpp
+      commandCompileToC(graph)
+    of backendObjc:
+      commandCompileToC(graph)
+    of backendJs:
+      when defined(leanCompiler):
+        globalError(conf, unknownLineInfo, "compiler wasn't built with JS code generator")
+      else:
+        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")
+        commandCompileToJS(graph)
+    of backendInvalid: doAssert false
+
   case conf.command.normalize
-  of "c", "cc", "compile", "compiletoc": handleC() # compile means compileToC currently
-  of "cpp", "compiletocpp":
-    conf.cmd = cmdCompileToCpp
-    if conf.exc == excNone: conf.exc = excCpp
-    defineSymbol(graph.config.symbols, "cpp")
-    commandCompileToC(graph)
-  of "objc", "compiletooc":
-    conf.cmd = cmdCompileToOC
-    defineSymbol(graph.config.symbols, "objc")
-    commandCompileToC(graph)
+  of "c", "cc", "compile", "compiletoc": handleBackend(backendC) # compile means compileToC currently
+  of "cpp", "compiletocpp": handleBackend(backendCpp)
+  of "objc", "compiletooc": handleBackend(backendObjc)
+  of "js", "compiletojs": handleBackend(backendJs)
   of "r": # different from `"run"`!
     conf.globalOptions.incl {optRun, optUseNimcache}
-    handleC()
+    handleBackend(conf.backend)
   of "run":
     conf.cmd = cmdRun
     when hasTinyCBackend:
@@ -216,18 +235,6 @@ proc mainCommand*(graph: ModuleGraph) =
       commandCompileToC(graph)
     else:
       rawMessage(conf, errGenerated, "'run' command not available; rebuild with -d:tinyc")
-  of "js", "compiletojs":
-    when defined(leanCompiler):
-      quit "compiler wasn't built with JS code generator"
-    else:
-      conf.cmd = cmdCompileToJS
-      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")
-      commandCompileToJS(graph)
   of "doc0":
     when defined(leanCompiler):
       quit "compiler wasn't built with documentation generator"
diff --git a/compiler/nim.nim b/compiler/nim.nim
index dba5b8bc3..b48fce0b8 100644
--- a/compiler/nim.nim
+++ b/compiler/nim.nim
@@ -88,17 +88,19 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
       tccgen.run(conf, conf.arguments)
   if optRun in conf.globalOptions:
     let output = conf.absOutFile
-    let ex = quoteShell output
     case conf.cmd
-    of cmdCompileToJS:
-      execExternalProgram(conf, findNodeJs() & " " & ex & ' ' & conf.arguments)
+    of cmdCompileToBackend:
+      var cmdPrefix = ""
+      case conf.backend
+      of backendC, backendCpp, backendObjc: discard
+      of backendJs: cmdPrefix = findNodeJs() & " "
+      else: doAssert false, $conf.backend
+      execExternalProgram(conf, cmdPrefix & output.quoteShell & ' ' & conf.arguments)
     of cmdDoc, cmdRst2html:
       if conf.arguments.len > 0:
         # reserved for future use
         rawMessage(conf, errGenerated, "'$1 cannot handle arguments" % [$conf.cmd])
       openDefaultBrowser($output)
-    of cmdCompileToC, cmdCompileToCpp, cmdCompileToOC:
-      execExternalProgram(conf, ex & ' ' & conf.arguments)
     else:
       # support as needed
       rawMessage(conf, errGenerated, "'$1 cannot handle --run" % [$conf.cmd])
diff --git a/compiler/options.nim b/compiler/options.nim
index 5475bc07b..3e6c7989f 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -105,11 +105,25 @@ const
                       optUseColors, optStdout}
 
 type
+  TBackend* = enum
+    backendInvalid = "" # for parseEnum
+    backendC = "c"
+    backendCpp = "cpp"  # was cmdCompileToCpp
+    backendJs = "js" # was cmdCompileToJS
+    backendObjc = "objc" # was cmdCompileToOC
+    # backendNimscript = "nimscript" # this could actually work
+    # backendLlvm = "llvm" # probably not well supported; was cmdCompileToLLVM
+
+type
   TCommands* = enum           # Nim's commands
                               # **keep binary compatible**
-    cmdNone, cmdCompileToC, cmdCompileToCpp, cmdCompileToOC,
-    cmdCompileToJS,
-    cmdCompileToLLVM, cmdInterpret, cmdPretty, cmdDoc,
+    cmdNone,
+    cmdCompileToC,            # deadcode
+    cmdCompileToCpp,          # deadcode
+    cmdCompileToOC,           # deadcode
+    cmdCompileToJS,           # deadcode
+    cmdCompileToLLVM,         # deadcode
+    cmdInterpret, cmdPretty, cmdDoc,
     cmdGenDepend, cmdDump,
     cmdCheck,                 # semantic checking for whole project
     cmdParse,                 # parse a single file (for debugging)
@@ -121,6 +135,7 @@ type
     cmdInteractive,           # start interactive session
     cmdRun,                   # run the project via TCC backend
     cmdJsonScript             # compile a .json build file
+    cmdCompileToBackend,      # compile to backend in TBackend
   TStringSeq* = seq[string]
   TGCMode* = enum             # the selected GC
     gcUnselected, gcNone, gcBoehm, gcRegions, gcMarkAndSweep, gcArc, gcOrc,
@@ -208,6 +223,7 @@ type
                           ## fields marked with '*' are subject to
                           ## the incremental compilation mechanisms
                           ## (+) means "part of the dependency"
+    backend*: TBackend
     target*: Target       # (+)
     linesCompiled*: int   # all lines that have been compiled
     options*: TOptions    # (+)
@@ -274,6 +290,7 @@ type
     docSeeSrcUrl*: string # if empty, no seeSrc will be generated. \
     # The string uses the formatting variables `path` and `line`.
     docRoot*: string ## see nim --fullhelp for --docRoot
+    docCmd*: string ## see nim --fullhelp for --docCmd
 
      # the used compiler
     cIncludes*: seq[AbsoluteDir]  # directories to search for included files
@@ -402,7 +419,7 @@ proc newConfigRef*(): ConfigRef =
     cIncludes: @[],   # directories to search for included files
     cLibs: @[],       # directories to search for lib files
     cLinkedLibs: @[],  # libraries to link
-
+    backend: backendC,
     externalToLink: @[],
     linkOptionsCmd: "",
     compileOptionsCmd: @[],
@@ -522,7 +539,10 @@ proc setConfigVar*(conf: ConfigRef; key, val: string) =
   conf.configVars[key] = val
 
 proc getOutFile*(conf: ConfigRef; filename: RelativeFile, ext: string): AbsoluteFile =
-  conf.outDir / changeFileExt(filename, ext)
+  # explains regression https://github.com/nim-lang/Nim/issues/6583#issuecomment-625711125
+  # Yet another reason why "" should not mean ".";  `""/something` should raise
+  # instead of implying "" == "." as it's bug prone.
+  result = conf.outDir / changeFileExt(filename, ext)
 
 proc absOutFile*(conf: ConfigRef): AbsoluteFile =
   if false:
@@ -615,7 +635,7 @@ proc getNimcacheDir*(conf: ConfigRef): AbsoluteDir =
   # XXX projectName should always be without a file extension!
   result = if not conf.nimcacheDir.isEmpty:
              conf.nimcacheDir
-           elif conf.cmd == cmdCompileToJS:
+           elif conf.backend == backendJs:
              conf.projectPath / genSubDir
            else:
             AbsoluteDir(getOsCacheDir() / splitFile(conf.projectName).name &
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index 465608b10..021a8b339 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -179,7 +179,7 @@ proc processImportCpp(c: PContext; s: PSym, extname: string, info: TLineInfo) =
   incl(s.flags, sfImportc)
   incl(s.flags, sfInfixCall)
   excl(s.flags, sfForward)
-  if c.config.cmd == cmdCompileToC:
+  if c.config.backend == backendC:
     let m = s.getModule()
     incl(m.flags, sfCompileToCpp)
   incl c.config.globalOptions, optMixedMode
@@ -797,8 +797,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
       of wExportc, wExportCpp:
         makeExternExport(c, sym, getOptionalStr(c, it, "$1"), it.info)
         if k == wExportCpp:
-          if c.config.cmd != cmdCompileToCpp:
-            localError(c.config, it.info, "exportcpp requires `nim cpp`, got " & $c.config.cmd)
+          if c.config.backend != backendCpp:
+            localError(c.config, it.info, "exportcpp requires `cpp` backend, got " & $c.config.backend)
           else:
             incl(sym.flags, sfMangleCpp)
         incl(sym.flags, sfUsed) # avoid wrong hints
@@ -819,7 +819,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
       of wImportCpp:
         processImportCpp(c, sym, getOptionalStr(c, it, "$1"), it.info)
       of wImportJs:
-        if c.config.cmd != cmdCompileToJS:
+        if c.config.backend != backendJs:
           localError(c.config, it.info, "`importjs` pragma requires the JavaScript target")
         let name = getOptionalStr(c, it, "$1")
         incl(sym.flags, sfImportc)
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index 56b6076d5..43fd1b69c 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -660,7 +660,7 @@ proc hasUnresolvedArgs(c: PContext, n: PNode): bool =
     return false
 
 proc newHiddenAddrTaken(c: PContext, n: PNode): PNode =
-  if n.kind == nkHiddenDeref and not (c.config.cmd == cmdCompileToCpp or
+  if n.kind == nkHiddenDeref and not (c.config.backend == backendCpp or
                                       sfCompileToCpp in c.module.flags):
     checkSonsLen(n, 1, c.config)
     result = n[0]
diff --git a/compiler/sizealignoffsetimpl.nim b/compiler/sizealignoffsetimpl.nim
index dc3fefeea..9d12297ea 100644
--- a/compiler/sizealignoffsetimpl.nim
+++ b/compiler/sizealignoffsetimpl.nim
@@ -346,7 +346,7 @@ proc computeSizeAlign(conf: ConfigRef; typ: PType) =
           while st.kind in skipPtrs:
             st = st[^1]
           computeSizeAlign(conf, st)
-          if conf.cmd == cmdCompileToCpp:
+          if conf.backend == backendCpp:
             OffsetAccum(
               offset: int(st.size) - int(st.paddingAtEnd),
               maxAlign: st.align
diff --git a/compiler/transf.nim b/compiler/transf.nim
index cbf904255..bd9f567ed 100644
--- a/compiler/transf.nim
+++ b/compiler/transf.nim
@@ -411,7 +411,7 @@ proc transformYield(c: PTransf, n: PNode): PNode =
 
 proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PNode =
   result = transformSons(c, n)
-  if c.graph.config.cmd == cmdCompileToCpp or sfCompileToCpp in c.module.flags: return
+  if c.graph.config.backend == backendCpp or sfCompileToCpp in c.module.flags: return
   var n = result
   case n[0].kind
   of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:
@@ -447,7 +447,7 @@ proc generateThunk(c: PTransf; prc: PNode, dest: PType): PNode =
 
   # we cannot generate a proper thunk here for GC-safety reasons
   # (see internal documentation):
-  if c.graph.config.cmd == cmdCompileToJS: return prc
+  if c.graph.config.backend == backendJs: return prc
   result = newNodeIT(nkClosure, prc.info, dest)
   var conv = newNodeIT(nkHiddenSubConv, prc.info, dest)
   conv.add(newNodeI(nkEmpty, prc.info))
diff --git a/compiler/vmops.nim b/compiler/vmops.nim
index 54b2877e7..27d1ef479 100644
--- a/compiler/vmops.nim
+++ b/compiler/vmops.nim
@@ -121,6 +121,7 @@ when defined(nimHasInvariant):
     of linkOptions: result = conf.linkOptions
     of compileOptions: result = conf.compileOptions
     of ccompilerPath: result = conf.cCompilerPath
+    of backend: result = $conf.backend
 
   proc querySettingSeqImpl(conf: ConfigRef, switch: BiggestInt): seq[string] =
     template copySeq(field: untyped): untyped =
@@ -215,7 +216,7 @@ proc registerAdditionalOps*(c: PCtx) =
 
   proc hashVmImpl(a: VmArgs) =
     var res = hashes.hash(a.getString(0), a.getInt(1).int, a.getInt(2).int)
-    if c.config.cmd == cmdCompileToJS:
+    if c.config.backend == backendJs:
       # emulate JS's terrible integers:
       res = cast[int32](res)
     setResult(a, res)
@@ -232,7 +233,7 @@ proc registerAdditionalOps*(c: PCtx) =
       bytes[i] = byte(arr[i].intVal and 0xff)
 
     var res = hashes.hash(bytes, sPos, ePos)
-    if c.config.cmd == cmdCompileToJS:
+    if c.config.backend == backendJs:
       # emulate JS's terrible integers:
       res = cast[int32](res)
     setResult(a, res)
diff --git a/doc/advopt.txt b/doc/advopt.txt
index c877b02e9..a668be5c0 100644
--- a/doc/advopt.txt
+++ b/doc/advopt.txt
@@ -5,6 +5,7 @@ Advanced commands:
   //js                      compile project to Javascript
   //e                       run a Nimscript file
   //rst2html                convert a reStructuredText file to HTML
+                            use `--docCmd:skip` to skip compiling snippets
   //rst2tex                 convert a reStructuredText file to TeX
   //jsondoc                 extract the documentation to a json file
   //ctags                   create a tags file
@@ -29,6 +30,8 @@ Runtime checks (see -x):
 Advanced options:
   -o:FILE, --out:FILE       set the output filename
   --outdir:DIR              set the path where the output file will be written
+  --usenimcache             will use `outdir=$$nimcache`, whichever it resolves
+                            to after all options have been processed
   --stdout:on|off           output to stdout
   --colors:on|off           turn compiler messages coloring on|off
   --listFullPaths:on|off    list full paths in messages
@@ -69,13 +72,17 @@ Advanced options:
   --clib:LIBNAME            link an additional C library
                             (you should omit platform-specific extensions)
   --project                 document the whole project (doc2)
-  --docRoot:path            nim doc --docRoot:/foo --project --outdir:docs /foo/sub/main.nim
+  --docRoot:path            `nim doc --docRoot:/foo --project --outdir:docs /foo/sub/main.nim`
                             generates: docs/sub/main.html
                             if path == @pkg, will use nimble file enclosing dir
-                            if path == @path, will use first matching dir in --path
+                            if path == @path, will use first matching dir in `--path`
                             if path == @default (the default and most useful), will use
                             best match among @pkg,@path.
                             if these are nonexistant, will use project path
+  -b, --backend:c|cpp|js|objc sets backend to use with commands like `nim doc` or `nim r`
+  --docCmd:cmd              if `cmd == skip`, skips runnableExamples
+                            else, runs runnableExamples with given options, eg:
+                            `--docCmd:"-d:foo --threads:on"`
   --docSeeSrcUrl:url        activate 'see source' for doc and doc2 commands
                             (see doc.item.seesrc in config/nimdoc.cfg)
   --docInternal             also generate documentation for non-exported symbols
diff --git a/doc/basicopt.txt b/doc/basicopt.txt
index cc808a077..cdd8ab9be 100644
--- a/doc/basicopt.txt
+++ b/doc/basicopt.txt
@@ -4,8 +4,10 @@
 
 Command:
   //compile, c                compile project with default code generator (C)
-  //r                         compile & run $nimcach/projname [arguments]
-  //doc                       generate the documentation for inputfile
+  //r                         compile to $nimcache/projname, run with [arguments]
+                              using backend specified by `--backend` (default: c)
+  //doc                       generate the documentation for inputfile for
+                              backend specified by `--backend` (default: c)
 
 Arguments:
   arguments are passed to the program being run (if --run option is selected)
diff --git a/lib/std/compilesettings.nim b/lib/std/compilesettings.nim
index 12204658d..b9b13175d 100644
--- a/lib/std/compilesettings.nim
+++ b/lib/std/compilesettings.nim
@@ -29,6 +29,8 @@ type
     linkOptions,      ## additional options passed to the linker
     compileOptions,   ## additional options passed to the C/C++ compiler
     ccompilerPath     ## the path to the C/C++ compiler
+    backend           ## the backend (eg: c|cpp|objc|js); both `nim doc --backend:js`
+                      ## and `nim js` would imply backend=js
 
   MultipleValueSetting* {.pure.} = enum ## \
                       ## settings resulting in a seq of string values
diff --git a/tests/nimdoc/m13129.nim b/tests/nimdoc/m13129.nim
new file mode 100644
index 000000000..df4b5a3f5
--- /dev/null
+++ b/tests/nimdoc/m13129.nim
@@ -0,0 +1,36 @@
+when defined(cpp):
+  {.push header: "<vector>".}
+  type
+    Vector[T] {.importcpp: "std::vector".} = object
+elif defined(js):
+  proc endsWith*(s, suffix: cstring): bool {.noSideEffect,importjs: "#.endsWith(#)".}
+elif defined(c):
+  proc c_printf*(frmt: cstring): cint {.
+    importc: "printf", header: "<stdio.h>", varargs, discardable.}
+
+proc main*() =
+  runnableExamples:
+    import std/compilesettings
+    doAssert not defined(m13129Foo1)
+    doAssert defined(m13129Foo2)
+    doAssert not defined(nimdoc)
+    echo "ok2: backend: " & querySetting(backend)
+    # echo defined(c), defined(js), 
+
+import std/compilesettings
+when defined nimdoc:
+  # import std/compilesettings
+  static:
+    doAssert defined(m13129Foo1)
+    doAssert not defined(m13129Foo2)
+    echo "ok1:" & querySetting(backend)
+
+when isMainModule:
+  when not defined(js):
+    import std/os
+    let cache = querySetting(nimcacheDir)
+    doAssert cache.len > 0
+    let app = getAppFilename()
+    doAssert app.isRelativeTo(cache)
+    doAssert querySetting(projectFull) == currentSourcePath
+    echo "ok3"
diff --git a/tests/nimdoc/readme.md b/tests/nimdoc/readme.md
new file mode 100644
index 000000000..64b16c2a5
--- /dev/null
+++ b/tests/nimdoc/readme.md
@@ -0,0 +1,2 @@
+the html validation is tested by nimdoc/tester.nim
+the runnableExamples + nim doc logic (across backend) is tested here
diff --git a/tests/trunner.nim b/tests/trunner.nim
index f71c324a2..e6a8a2077 100644
--- a/tests/trunner.nim
+++ b/tests/trunner.nim
@@ -6,7 +6,7 @@ discard """
 ## tests that don't quite fit the mold and are easier to handle via `execCmdEx`
 ## A few others could be added to here to simplify code.
 
-import std/[strformat,os,osproc]
+import std/[strformat,os,osproc,unittest]
 
 const nim = getCurrentCompilerExe()
 
@@ -15,8 +15,9 @@ const mode =
   elif defined(cpp): "cpp"
   else: static: doAssert false
 
+const testsDir = currentSourcePath().parentDir
+
 proc runCmd(file, options = ""): auto =
-  const testsDir = currentSourcePath().parentDir
   let fileabs = testsDir / file.unixToNativePath
   doAssert fileabs.existsFile, fileabs
   let cmd = fmt"{nim} {mode} {options} --hints:off {fileabs}"
@@ -55,11 +56,11 @@ ret=[s1:foobar s2:foobar age:25 pi:3.14]
 
 else: # don't run twice the same test
   import std/[strutils]
-  template check(msg) = doAssert msg in output, output
+  template check2(msg) = doAssert msg in output, output
 
   block: # mstatic_assert
     let (output, exitCode) = runCmd("ccgbugs/mstatic_assert.nim", "-d:caseBad")
-    check "sizeof(bool) == 2"
+    check2 "sizeof(bool) == 2"
     doAssert exitCode != 0
 
   block: # ABI checks
@@ -72,11 +73,11 @@ else: # don't run twice the same test
       # on platforms that support _StaticAssert natively, errors will show full context, eg:
       # error: static_assert failed due to requirement 'sizeof(unsigned char) == 8'
       # "backend & Nim disagree on size for: BadImportcType{int64} [declared in mabi_check.nim(1, 6)]"
-      check "sizeof(unsigned char) == 8"
-      check "sizeof(struct Foo2) == 1"
-      check "sizeof(Foo5) == 16"
-      check "sizeof(Foo5) == 3"
-      check "sizeof(struct Foo6) == "
+      check2 "sizeof(unsigned char) == 8"
+      check2 "sizeof(struct Foo2) == 1"
+      check2 "sizeof(Foo5) == 16"
+      check2 "sizeof(Foo5) == 3"
+      check2 "sizeof(struct Foo6) == "
       doAssert exitCode != 0
 
   import streams
@@ -103,3 +104,21 @@ else: # don't run twice the same test
         var (output, exitCode) = execCmdEx(cmd)
         output.stripLineEnd
         doAssert output == expected
+
+  block: # nim doc --backend:$backend --doccmd:$cmd
+    # test for https://github.com/nim-lang/Nim/issues/13129
+    # test for https://github.com/nim-lang/Nim/issues/13891
+    const buildDir = testsDir.parentDir / "build"
+    const nimcache = buildDir / "nimcacheTrunner"
+      # `querySetting(nimcacheDir)` would also be possible, but we thus
+      # avoid stomping on other parallel tests
+    let file = testsDir / "nimdoc/m13129.nim"
+    for backend in fmt"{mode} js".split:
+      let cmd = fmt"{nim} doc -b:{backend} --nimcache:{nimcache} -d:m13129Foo1 --doccmd:'-d:m13129Foo2 --hints:off' --usenimcache --hints:off {file}"
+      check execCmdEx(cmd) == (&"ok1:{backend}\nok2: backend: {backend}\n", 0)
+    # checks that --usenimcache works with `nim doc`
+    check fileExists(nimcache / "m13129.html")
+
+    block: # mak sure --backend works with `nim r`
+      let cmd = fmt"{nim} r --backend:{mode} --hints:off --nimcache:{nimcache} {file}"
+      check execCmdEx(cmd) == ("ok3\n", 0)
diff --git a/tests/vm/tcompilesetting.nim b/tests/vm/tcompilesetting.nim
index 79527d584..98565ab94 100644
--- a/tests/vm/tcompilesetting.nim
+++ b/tests/vm/tcompilesetting.nim
@@ -17,3 +17,4 @@ static:
 
 doAssert "myNimCache" in nc
 doAssert "myNimblePath" in np[0]
+doAssert querySetting(backend) == "c"