nimconf.nim 11 KB

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