nimconf.nim 9.5 KB

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