nimconf.nim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2012 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. # This module handles the reading of the config file.
  10. import
  11. llstream, commands, msgs, lexer, ast,
  12. options, idents, wordrecg, lineinfos, pathutils, scriptconfig
  13. import std/[os, strutils, strtabs]
  14. when defined(nimPreviewSlimSystem):
  15. import std/syncio
  16. # ---------------- configuration file parser -----------------------------
  17. # we use Nim's lexer here to save space and work
  18. proc ppGetTok(L: var Lexer, tok: var Token) =
  19. # simple filter
  20. rawGetTok(L, tok)
  21. while tok.tokType in {tkComment}: rawGetTok(L, tok)
  22. proc parseExpr(L: var Lexer, tok: var Token; config: ConfigRef): bool
  23. proc parseAtom(L: var Lexer, tok: var Token; config: ConfigRef): bool =
  24. if tok.tokType == tkParLe:
  25. ppGetTok(L, tok)
  26. result = parseExpr(L, tok, config)
  27. if tok.tokType == tkParRi: ppGetTok(L, tok)
  28. else: lexMessage(L, errGenerated, "expected closing ')'")
  29. elif tok.tokType == tkNot:
  30. ppGetTok(L, tok)
  31. result = not parseAtom(L, tok, config)
  32. else:
  33. result = isDefined(config, tok.ident.s)
  34. ppGetTok(L, tok)
  35. proc parseAndExpr(L: var Lexer, tok: var Token; config: ConfigRef): bool =
  36. result = parseAtom(L, tok, config)
  37. while tok.tokType == tkAnd:
  38. ppGetTok(L, tok) # skip "and"
  39. var b = parseAtom(L, tok, config)
  40. result = result and b
  41. proc parseExpr(L: var Lexer, tok: var Token; config: ConfigRef): bool =
  42. result = parseAndExpr(L, tok, config)
  43. while tok.tokType == tkOr:
  44. ppGetTok(L, tok) # skip "or"
  45. var b = parseAndExpr(L, tok, config)
  46. result = result or b
  47. proc evalppIf(L: var Lexer, tok: var Token; config: ConfigRef): bool =
  48. ppGetTok(L, tok) # skip 'if' or 'elif'
  49. result = parseExpr(L, tok, config)
  50. if tok.tokType == tkColon: ppGetTok(L, tok)
  51. else: lexMessage(L, errGenerated, "expected ':'")
  52. #var condStack: seq[bool] = @[]
  53. proc doEnd(L: var Lexer, tok: var Token; condStack: var seq[bool]) =
  54. if high(condStack) < 0: lexMessage(L, errGenerated, "expected @if")
  55. ppGetTok(L, tok) # skip 'end'
  56. setLen(condStack, high(condStack))
  57. type
  58. TJumpDest = enum
  59. jdEndif, jdElseEndif
  60. proc jumpToDirective(L: var Lexer, tok: var Token, dest: TJumpDest; config: ConfigRef;
  61. condStack: var seq[bool])
  62. proc doElse(L: var Lexer, tok: var Token; config: ConfigRef; condStack: var seq[bool]) =
  63. if high(condStack) < 0: lexMessage(L, errGenerated, "expected @if")
  64. ppGetTok(L, tok)
  65. if tok.tokType == tkColon: ppGetTok(L, tok)
  66. if condStack[high(condStack)]: jumpToDirective(L, tok, jdEndif, config, condStack)
  67. proc doElif(L: var Lexer, tok: var Token; config: ConfigRef; condStack: var seq[bool]) =
  68. if high(condStack) < 0: lexMessage(L, errGenerated, "expected @if")
  69. var res = evalppIf(L, tok, config)
  70. if condStack[high(condStack)] or not res: jumpToDirective(L, tok, jdElseEndif, config, condStack)
  71. else: condStack[high(condStack)] = true
  72. proc jumpToDirective(L: var Lexer, tok: var Token, dest: TJumpDest; config: ConfigRef;
  73. condStack: var seq[bool]) =
  74. var nestedIfs = 0
  75. while true:
  76. if tok.ident != nil and tok.ident.s == "@":
  77. ppGetTok(L, tok)
  78. case whichKeyword(tok.ident)
  79. of wIf:
  80. inc(nestedIfs)
  81. of wElse:
  82. if dest == jdElseEndif and nestedIfs == 0:
  83. doElse(L, tok, config, condStack)
  84. break
  85. of wElif:
  86. if dest == jdElseEndif and nestedIfs == 0:
  87. doElif(L, tok, config, condStack)
  88. break
  89. of wEnd:
  90. if nestedIfs == 0:
  91. doEnd(L, tok, condStack)
  92. break
  93. if nestedIfs > 0: dec(nestedIfs)
  94. else:
  95. discard
  96. ppGetTok(L, tok)
  97. elif tok.tokType == tkEof:
  98. lexMessage(L, errGenerated, "expected @end")
  99. else:
  100. ppGetTok(L, tok)
  101. proc parseDirective(L: var Lexer, tok: var Token; config: ConfigRef; condStack: var seq[bool]) =
  102. ppGetTok(L, tok) # skip @
  103. case whichKeyword(tok.ident)
  104. of wIf:
  105. setLen(condStack, condStack.len + 1)
  106. let res = evalppIf(L, tok, config)
  107. condStack[high(condStack)] = res
  108. if not res: jumpToDirective(L, tok, jdElseEndif, config, condStack)
  109. of wElif: doElif(L, tok, config, condStack)
  110. of wElse: doElse(L, tok, config, condStack)
  111. of wEnd: doEnd(L, tok, condStack)
  112. of wWrite:
  113. ppGetTok(L, tok)
  114. msgs.msgWriteln(config, strtabs.`%`($tok, config.configVars,
  115. {useEnvironment, useKey}))
  116. ppGetTok(L, tok)
  117. else:
  118. case tok.ident.s.normalize
  119. of "putenv":
  120. ppGetTok(L, tok)
  121. var key = $tok
  122. ppGetTok(L, tok)
  123. os.putEnv(key, $tok)
  124. ppGetTok(L, tok)
  125. of "prependenv":
  126. ppGetTok(L, tok)
  127. var key = $tok
  128. ppGetTok(L, tok)
  129. os.putEnv(key, $tok & os.getEnv(key))
  130. ppGetTok(L, tok)
  131. of "appendenv":
  132. ppGetTok(L, tok)
  133. var key = $tok
  134. ppGetTok(L, tok)
  135. os.putEnv(key, os.getEnv(key) & $tok)
  136. ppGetTok(L, tok)
  137. else:
  138. lexMessage(L, errGenerated, "invalid directive: '$1'" % $tok)
  139. proc confTok(L: var Lexer, tok: var Token; config: ConfigRef; condStack: var seq[bool]) =
  140. ppGetTok(L, tok)
  141. while tok.ident != nil and tok.ident.s == "@":
  142. parseDirective(L, tok, config, condStack) # else: give the token to the parser
  143. proc checkSymbol(L: Lexer, tok: Token) =
  144. if tok.tokType notin {tkSymbol..tkInt64Lit, tkStrLit..tkTripleStrLit}:
  145. lexMessage(L, errGenerated, "expected identifier, but got: " & $tok)
  146. proc parseAssignment(L: var Lexer, tok: var Token;
  147. config: ConfigRef; filename: AbsoluteFile; condStack: var seq[bool]) =
  148. if tok.ident != nil:
  149. if tok.ident.s == "-" or tok.ident.s == "--":
  150. confTok(L, tok, config, condStack) # skip unnecessary prefix
  151. var info = getLineInfo(L, tok) # save for later in case of an error
  152. checkSymbol(L, tok)
  153. var s = $tok
  154. confTok(L, tok, config, condStack) # skip symbol
  155. var val = ""
  156. while tok.tokType == tkDot:
  157. s.add('.')
  158. confTok(L, tok, config, condStack)
  159. checkSymbol(L, tok)
  160. s.add($tok)
  161. confTok(L, tok, config, condStack)
  162. if tok.tokType == tkBracketLe:
  163. # BUGFIX: val, not s!
  164. confTok(L, tok, config, condStack)
  165. checkSymbol(L, tok)
  166. val.add('[')
  167. val.add($tok)
  168. confTok(L, tok, config, condStack)
  169. if tok.tokType == tkBracketRi: confTok(L, tok, config, condStack)
  170. else: lexMessage(L, errGenerated, "expected closing ']'")
  171. val.add(']')
  172. let percent = tok.ident != nil and tok.ident.s == "%="
  173. if tok.tokType in {tkColon, tkEquals} or percent:
  174. if val.len > 0: val.add(':')
  175. confTok(L, tok, config, condStack) # skip ':' or '=' or '%'
  176. checkSymbol(L, tok)
  177. val.add($tok)
  178. confTok(L, tok, config, condStack) # skip symbol
  179. if tok.tokType in {tkColon, tkEquals}:
  180. val.add($tok) # add the :
  181. confTok(L, tok, config, condStack) # skip symbol
  182. checkSymbol(L, tok)
  183. val.add($tok) # add the token after it
  184. confTok(L, tok, config, condStack) # skip symbol
  185. while tok.ident != nil and tok.ident.s == "&":
  186. confTok(L, tok, config, condStack)
  187. checkSymbol(L, tok)
  188. val.add($tok)
  189. confTok(L, tok, config, condStack)
  190. config.currentConfigDir = parentDir(filename.string)
  191. if percent:
  192. processSwitch(s, strtabs.`%`(val, config.configVars,
  193. {useEnvironment, useEmpty}), passPP, info, config)
  194. else:
  195. processSwitch(s, val, passPP, info, config)
  196. proc readConfigFile*(filename: AbsoluteFile; cache: IdentCache;
  197. config: ConfigRef): bool =
  198. var
  199. L: Lexer = default(Lexer)
  200. tok: Token
  201. stream: PLLStream
  202. stream = llStreamOpen(filename, fmRead)
  203. if stream != nil:
  204. openLexer(L, filename, stream, cache, config)
  205. tok = Token(tokType: tkEof) # to avoid a pointless warning
  206. var condStack: seq[bool] = @[]
  207. confTok(L, tok, config, condStack) # read in the first token
  208. while tok.tokType != tkEof: parseAssignment(L, tok, config, filename, condStack)
  209. if condStack.len > 0: lexMessage(L, errGenerated, "expected @end")
  210. closeLexer(L)
  211. return true
  212. else:
  213. result = false
  214. proc getUserConfigPath*(filename: RelativeFile): AbsoluteFile =
  215. result = getConfigDir().AbsoluteDir / RelativeDir"nim" / filename
  216. proc getSystemConfigPath*(conf: ConfigRef; filename: RelativeFile): AbsoluteFile =
  217. # try standard configuration file (installation did not distribute files
  218. # the UNIX way)
  219. let p = getPrefixDir(conf)
  220. result = p / RelativeDir"config" / filename
  221. when defined(unix):
  222. if not fileExists(result): result = p / RelativeDir"etc/nim" / filename
  223. if not fileExists(result): result = AbsoluteDir"/etc/nim" / filename
  224. proc loadConfigs*(cfg: RelativeFile; cache: IdentCache; conf: ConfigRef; idgen: IdGenerator) =
  225. setDefaultLibpath(conf)
  226. template readConfigFile(path) =
  227. let configPath = path
  228. if readConfigFile(configPath, cache, conf):
  229. conf.configFiles.add(configPath)
  230. template runNimScriptIfExists(path: AbsoluteFile, isMain = false) =
  231. let p = path # eval once
  232. var s: PLLStream = nil
  233. if isMain and optWasNimscript in conf.globalOptions:
  234. if conf.projectIsStdin: s = stdin.llStreamOpen
  235. elif conf.projectIsCmd: s = llStreamOpen(conf.cmdInput)
  236. if s == nil and fileExists(p): s = llStreamOpen(p, fmRead)
  237. if s != nil:
  238. conf.configFiles.add(p)
  239. runNimScript(cache, p, idgen, freshDefines = false, conf, s)
  240. if optSkipSystemConfigFile notin conf.globalOptions:
  241. readConfigFile(getSystemConfigPath(conf, cfg))
  242. if cfg == DefaultConfig:
  243. runNimScriptIfExists(getSystemConfigPath(conf, DefaultConfigNims))
  244. if optSkipUserConfigFile notin conf.globalOptions:
  245. readConfigFile(getUserConfigPath(cfg))
  246. if cfg == DefaultConfig:
  247. runNimScriptIfExists(getUserConfigPath(DefaultConfigNims))
  248. let pd = if not conf.projectPath.isEmpty: conf.projectPath else: AbsoluteDir(getCurrentDir())
  249. if optSkipParentConfigFiles notin conf.globalOptions:
  250. for dir in parentDirs(pd.string, fromRoot=true, inclusive=false):
  251. readConfigFile(AbsoluteDir(dir) / cfg)
  252. if cfg == DefaultConfig:
  253. runNimScriptIfExists(AbsoluteDir(dir) / DefaultConfigNims)
  254. if optSkipProjConfigFile notin conf.globalOptions:
  255. readConfigFile(pd / cfg)
  256. if cfg == DefaultConfig:
  257. runNimScriptIfExists(pd / DefaultConfigNims)
  258. if conf.projectName.len != 0:
  259. # new project wide config file:
  260. var projectConfig = changeFileExt(conf.projectFull, "nimcfg")
  261. if not fileExists(projectConfig):
  262. projectConfig = changeFileExt(conf.projectFull, "nim.cfg")
  263. readConfigFile(projectConfig)
  264. let scriptFile = conf.projectFull.changeFileExt("nims")
  265. let scriptIsProj = scriptFile == conf.projectFull
  266. template showHintConf =
  267. for filename in conf.configFiles:
  268. # delayed to here so that `hintConf` is honored
  269. rawMessage(conf, hintConf, filename.string)
  270. if conf.cmd == cmdNimscript:
  271. showHintConf()
  272. conf.configFiles.setLen 0
  273. if conf.cmd notin {cmdIdeTools, cmdCheck, cmdDump}:
  274. if conf.cmd == cmdNimscript:
  275. runNimScriptIfExists(conf.projectFull, isMain = true)
  276. else:
  277. runNimScriptIfExists(scriptFile, isMain = true)
  278. else:
  279. if not scriptIsProj:
  280. runNimScriptIfExists(scriptFile, isMain = true)
  281. else:
  282. # 'nimsuggest foo.nims' means to just auto-complete the NimScript file
  283. # `nim check foo.nims' means to check the syntax of the NimScript file
  284. discard
  285. showHintConf()