# # # The Nim Compiler # (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # # This module handles the parsing of command line arguments. # We do this here before the 'import' statement so 'defined' does not get # confused with 'TGCMode.gcGenerational' etc. template bootSwitch(name, expr, userString) = # Helper to build boot constants, for debugging you can 'echo' the else part. const name = if expr: " " & userString else: "" bootSwitch(usedRelease, defined(release), "-d:release") bootSwitch(usedGnuReadline, defined(useLinenoise), "-d:useLinenoise") bootSwitch(usedBoehm, defined(boehmgc), "--gc:boehm") bootSwitch(usedMarkAndSweep, defined(gcmarkandsweep), "--gc:markAndSweep") bootSwitch(usedGenerational, defined(gcgenerational), "--gc:generational") bootSwitch(usedGoGC, defined(gogc), "--gc:go") bootSwitch(usedNoGC, defined(nogc), "--gc:none") import os, msgs, options, nversion, condsyms, strutils, extccomp, platform, wordrecg, parseutils, nimblecmd, idents, parseopt, sequtils, lineinfos, pathutils # but some have deps to imported modules. Yay. bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc") bootSwitch(usedNativeStacktrace, defined(nativeStackTrace) and nativeStackTraceSupported, "-d:nativeStackTrace") bootSwitch(usedFFI, hasFFI, "-d:useFFI") type TCmdLinePass* = enum passCmd1, # first pass over the command line passCmd2, # second pass over the command line passPP # preprocessor called processCommand() const HelpMessage = "Nim Compiler Version $1 [$2: $3]\n" & "Compiled at $4\n" & "Copyright (c) 2006-" & copyrightYear & " by Andreas Rumpf\n" const Usage = slurp"../doc/basicopt.txt".replace("//", "") FeatureDesc = block: var x = "" for f in low(Feature)..high(Feature): if x.len > 0: x.add "|" x.add $f x AdvancedUsage = slurp"../doc/advopt.txt".replace("//", "") % FeatureDesc proc getCommandLineDesc(conf: ConfigRef): string = result = (HelpMessage % [VersionAsString, platform.OS[conf.target.hostOS].name, CPU[conf.target.hostCPU].name, CompileDate]) & Usage proc helpOnError(conf: ConfigRef; pass: TCmdLinePass) = if pass == passCmd1: msgWriteln(conf, getCommandLineDesc(conf), {msgStdout}) msgQuit(0) proc writeAdvancedUsage(conf: ConfigRef; pass: TCmdLinePass) = if pass == passCmd1: msgWriteln(conf, (HelpMessage % [VersionAsString, platform.OS[conf.target.hostOS].name, CPU[conf.target.hostCPU].name, CompileDate]) & AdvancedUsage, {msgStdout}) msgQuit(0) proc writeFullhelp(conf: ConfigRef; pass: TCmdLinePass) = if pass == passCmd1: msgWriteln(conf, `%`(HelpMessage, [VersionAsString, platform.OS[conf.target.hostOS].name, CPU[conf.target.hostCPU].name, CompileDate]) & Usage & AdvancedUsage, {msgStdout}) msgQuit(0) proc writeVersionInfo(conf: ConfigRef; pass: TCmdLinePass) = if pass == passCmd1: msgWriteln(conf, `%`(HelpMessage, [VersionAsString, platform.OS[conf.target.hostOS].name, CPU[conf.target.hostCPU].name, CompileDate]), {msgStdout}) const gitHash = gorge("git log -n 1 --format=%H").strip when gitHash.len == 40: msgWriteln(conf, "git hash: " & gitHash, {msgStdout}) msgWriteln(conf, "active boot switches:" & usedRelease & usedTinyC & usedGnuReadline & usedNativeStacktrace & usedFFI & usedBoehm & usedMarkAndSweep & usedGenerational & usedGoGC & usedNoGC, {msgStdout}) msgQuit(0) proc writeCommandLineUsage*(conf: ConfigRef; helpWritten: var bool) = if not helpWritten: msgWriteln(conf, getCommandLineDesc(conf), {msgStdout}) helpWritten = true proc addPrefix(switch: string): string = if len(switch) == 1: result = "-" & switch else: result = "--" & switch const errInvalidCmdLineOption = "invalid command line option: '$1'" errOnOrOffExpectedButXFound = "'on' or 'off' expected, but '$1' found" errOnOffOrListExpectedButXFound = "'on', 'off' or 'list' expected, but '$1' found" proc invalidCmdLineOption(conf: ConfigRef; pass: TCmdLinePass, switch: string, info: TLineInfo) = if switch == " ": localError(conf, info, errInvalidCmdLineOption % "-") else: localError(conf, info, errInvalidCmdLineOption % addPrefix(switch)) proc splitSwitch(conf: ConfigRef; switch: string, cmd, arg: var string, pass: TCmdLinePass, info: TLineInfo) = cmd = "" var i = 0 if i < len(switch) and switch[i] == '-': inc(i) if i < len(switch) and switch[i] == '-': inc(i) while i < len(switch): case switch[i] of 'a'..'z', 'A'..'Z', '0'..'9', '_', '.': add(cmd, switch[i]) else: break inc(i) if i >= len(switch): arg = "" # cmd:arg => (cmd,arg) elif switch[i] in {':', '='}: arg = substr(switch, i + 1) # cmd[sub]:rest => (cmd,[sub]:rest) elif switch[i] == '[': arg = substr(switch, i) else: invalidCmdLineOption(conf, pass, switch, info) proc processOnOffSwitch(conf: ConfigRef; op: TOptions, arg: string, pass: TCmdLinePass, info: TLineInfo) = case arg.normalize of "on": conf.options = conf.optio
discard """
  file: "tstringinterp.nim"
  output: "Hello Alice, 64 | Hello Bob, 10$"
"""

import macros, parseutils, strutils

proc concat(strings: varargs[string]): string =
  result = newString(0)
  for s in items(strings): result.add(s)

template processInterpolations(e) =
  var s = e[1].strVal
  for f in interpolatedFragments(s):
    case f.kind
    of ikStr:         addString(f.value)
    of ikDollar:      addDollar()
    of ikVar, ikExpr: addExpr(newCall("$", parseExpr(f.value)))

macro formatStyleInterpolation(e: untyped): untyped =
  let e = callsite()
  var
    formatString = ""
    arrayNode = newNimNode(nnkBracket)
    idx = 1

  proc addString(s: string) =
    formatString.add(s)

  proc addExpr(e: NimNode) =
    arrayNode.add(e)
    formatString.add("$" & $(idx))
    inc idx

  proc addDollar() =
    formatString.add("$$")

  processInterpolations(e)

  result = parseExpr("\"x\" % [y]")
  result[1].strVal = formatString
  result[2] = arrayNode

macro concatStyleInterpolation(e: untyped): untyped =
  let e = callsite()
  var args: seq[NimNode]
  newSeq(args, 0)

  proc addString(s: string)    = args.add(newStrLitNode(s))
  proc addExpr(e: NimNode) = args.add(e)
  proc addDollar()             = args.add(newStrLitNode"$")

  processInterpolations(e)

  result = newCall("concat", args)

###

proc sum(a, b, c: int): int =
  return (a + b + c)

var
  alice = "Alice"
  bob = "Bob"
  a = 10
  b = 20
  c = 34

var
  s1 = concatStyleInterpolation"Hello ${alice}, ${sum(a, b, c)}"
  s2 = formatStyleInterpolation"Hello ${bob}, ${sum(alice.len, bob.len, 2)}$$"

write(stdout, s1 & " | " & s2)
, "t": expectArg(conf, switch, arg, pass, info) if pass in {passCmd2, passPP}: extccomp.addCompileOptionCmd(conf, arg) of "passl", "l": expectArg(conf, switch, arg, pass, info) if pass in {passCmd2, passPP}: extccomp.addLinkOptionCmd(conf, arg) of "cincludes": expectArg(conf, switch, arg, pass, info) if pass in {passCmd2, passPP}: conf.cIncludes.add processPath(conf, arg, info) of "clibdir": expectArg(conf, switch, arg, pass, info) if pass in {passCmd2, passPP}: conf.cLibs.add processPath(conf, arg, info) of "clib": expectArg(conf, switch, arg, pass, info) if pass in {passCmd2, passPP}: conf.cLinkedLibs.add processPath(conf, arg, info).string of "header": if conf != nil: conf.headerFile = arg incl(conf.globalOptions, optGenIndex) of "index": processOnOffSwitchG(conf, {optGenIndex}, arg, pass, info) of "import": expectArg(conf, switch, arg, pass, info) if pass in {passCmd2, passPP}: conf.implicitImports.add arg of "include": expectArg(conf, switch, arg, pass, info) if pass in {passCmd2, passPP}: conf.implicitIncludes.add arg of "listcmd": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optListCmd) of "genmapping": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optGenMapping) of "os": expectArg(conf, switch, arg, pass, info) if pass in {passCmd1, passPP}: let theOS = platform.nameToOS(arg) if theOS == osNone: localError(conf, info, "unknown OS: '$1'" % arg) elif theOS != conf.target.hostOS: setTarget(conf.target, theOS, conf.target.targetCPU) of "cpu": expectArg(conf, switch, arg, pass, info) if pass in {passCmd1, passPP}: let cpu = platform.nameToCPU(arg) if cpu == cpuNone: localError(conf, info, "unknown CPU: '$1'" % arg) elif cpu != conf.target.hostCPU: setTarget(conf.target, conf.target.targetOS, cpu) of "run", "r": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optRun) of "verbosity": expectArg(conf, switch, arg, pass, info) let verbosity = parseInt(arg) if verbosity notin {0..3}: localError(conf, info, "invalid verbosity level: '$1'" % arg) conf.verbosity = verbosity conf.notes = NotesVerbosity[conf.verbosity] incl(conf.notes, conf.enableNotes) excl(conf.notes, conf.disableNotes) conf.mainPackageNotes = conf.notes of "parallelbuild": expectArg(conf, switch, arg, pass, info) conf.numberOfProcessors = parseInt(arg) of "version", "v": expectNoArg(conf, switch, arg, pass, info) writeVersionInfo(conf, pass) of "advanced": expectNoArg(conf, switch, arg, pass, info) writeAdvancedUsage(conf, pass) of "fullhelp": expectNoArg(conf, switch, arg, pass, info) writeFullhelp(conf, pass) of "help", "h": expectNoArg(conf, switch, arg, pass, info) helpOnError(conf, pass) of "symbolfiles", "incremental": case arg.normalize of "on": conf.symbolFiles = v2Sf of "off": conf.symbolFiles = disabledSf of "writeonly": conf.symbolFiles = writeOnlySf of "readonly": conf.symbolFiles = readOnlySf of "v2": conf.symbolFiles = v2Sf else: localError(conf, info, "invalid option for --symbolFiles: " & arg) of "skipcfg": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optSkipSystemConfigFile) of "skipprojcfg": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optSkipProjConfigFile) of "skipusercfg": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optSkipUserConfigFile) of "skipparentcfg": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optSkipParentConfigFiles) of "genscript", "gendeps": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optGenScript) incl(conf.globalOptions, optCompileOnly) of "colors": processOnOffSwitchG(conf, {optUseColors}, arg, pass, info) of "lib": expectArg(conf, switch, arg, pass, info) conf.libpath = processPath(conf, arg, info, notRelativeToProj=true) of "putenv": expectArg(conf, switch, arg, pass, info) splitSwitch(conf, arg, key, val, pass, info) os.putEnv(key, val) of "cc": expectArg(conf, switch, arg, pass, info) setCC(conf, arg, info) of "track": expectArg(conf, switch, arg, pass, info) track(conf, arg, info) of "trackdirty": expectArg(conf, switch, arg, pass, info) trackDirty(conf, arg, info) of "suggest": expectNoArg(conf, switch, arg, pass, info) conf.ideCmd = ideSug of "def": expectNoArg(conf, switch, arg, pass, info) conf.ideCmd = ideDef of "eval": expectArg(conf, switch, arg, pass, info) conf.evalExpr = arg of "context": expectNoArg(conf, switch, arg, pass, info) conf.ideCmd = ideCon of "usages": expectNoArg(conf, switch, arg, pass, info) conf.ideCmd = ideUse of "stdout": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optStdout) of "listfullpaths": expectNoArg(conf, switch, arg, pass, info) incl conf.globalOptions, optListFullPaths of "dynliboverride": dynlibOverride(conf, switch, arg, pass, info) of "dynliboverrideall": expectNoArg(conf, switch, arg, pass, info) incl conf.globalOptions, optDynlibOverrideAll of "cs": # only supported for compatibility. Does nothing. expectArg(conf, switch, arg, pass, info) of "experimental": if arg.len == 0: conf.features.incl oldExperimentalFeatures else: try: conf.features.incl parseEnum[Feature](arg) except ValueError: localError(conf, info, "unknown experimental feature") of "nocppexceptions": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optNoCppExceptions) defineSymbol(conf.symbols, "noCppExceptions") of "cppdefine": expectArg(conf, switch, arg, pass, info) if conf != nil: conf.cppDefine(arg) of "newruntime": expectNoArg(conf, switch, arg, pass, info) doAssert(conf != nil) incl(conf.features, destructor) defineSymbol(conf.symbols, "nimNewRuntime") of "nep1": processOnOffSwitchG(conf, {optCheckNep1}, arg, pass, info) of "cppcompiletonamespace": if arg.len > 0: conf.cppCustomNamespace = arg else: conf.cppCustomNamespace = "Nim" defineSymbol(conf.symbols, "cppCompileToNamespace", conf.cppCustomNamespace) else: if strutils.find(switch, '.') >= 0: options.setConfigVar(conf, switch, arg) else: invalidCmdLineOption(conf, pass, switch, info) template gCmdLineInfo*(): untyped = newLineInfo(config, AbsoluteFile"command line", 1, 1) proc processCommand*(switch: string, pass: TCmdLinePass; config: ConfigRef) = var cmd, arg: string splitSwitch(config, switch, cmd, arg, pass, gCmdLineInfo) processSwitch(cmd, arg, pass, gCmdLineInfo, config) proc processSwitch*(pass: TCmdLinePass; p: OptParser; config: ConfigRef) = # hint[X]:off is parsed as (p.key = "hint[X]", p.val = "off") # we transform it to (key = hint, val = [X]:off) var bracketLe = strutils.find(p.key, '[') if bracketLe >= 0: var key = substr(p.key, 0, bracketLe - 1) var val = substr(p.key, bracketLe) & ':' & p.val processSwitch(key, val, pass, gCmdLineInfo, config) else: processSwitch(p.key, p.val, pass, gCmdLineInfo, config) proc processArgument*(pass: TCmdLinePass; p: OptParser; argsCount: var int; config: ConfigRef): bool = if argsCount == 0: # nim filename.nims is the same as "nim e filename.nims": if p.key.endswith(".nims"): config.command = "e" config.projectName = unixToNativePath(p.key) config.arguments = cmdLineRest(p) result = true elif pass != passCmd2: config.command = p.key else: if pass == passCmd1: config.commandArgs.add p.key if argsCount == 1: # support UNIX style filenames everywhere for portable build scripts: config.projectName = unixToNativePath(p.key) config.arguments = cmdLineRest(p) result = true inc argsCount