evaltempl.nim 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2013 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Template evaluation engine. Now hygienic.
  10. import
  11. strutils, options, ast, astalgo, msgs, os, idents, wordrecg, renderer,
  12. lineinfos
  13. type
  14. TemplCtx = object
  15. owner, genSymOwner: PSym
  16. instLines: bool # use the instantiation lines numbers
  17. isDeclarative: bool
  18. mapping: TIdTable # every gensym'ed symbol needs to be mapped to some
  19. # new symbol
  20. config: ConfigRef
  21. proc copyNode(ctx: TemplCtx, a, b: PNode): PNode =
  22. result = copyNode(a)
  23. if ctx.instLines: result.info = b.info
  24. proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) =
  25. template handleParam(param) =
  26. let x = param
  27. if x.kind == nkArgList:
  28. for y in items(x): result.add(y)
  29. else:
  30. result.add copyTree(x)
  31. case templ.kind
  32. of nkSym:
  33. var s = templ.sym
  34. if s.owner == nil or s.owner.id == c.owner.id:
  35. if s.kind == skParam and sfGenSym notin s.flags:
  36. handleParam actual.sons[s.position]
  37. elif (s.owner != nil) and (s.kind == skGenericParam or
  38. s.kind == skType and s.typ != nil and s.typ.kind == tyGenericParam):
  39. handleParam actual.sons[s.owner.typ.len + s.position - 1]
  40. else:
  41. internalAssert c.config, sfGenSym in s.flags or s.kind == skType
  42. var x = PSym(idTableGet(c.mapping, s))
  43. if x == nil:
  44. x = copySym(s)
  45. # sem'check needs to set the owner properly later, see bug #9476
  46. x.owner = nil # c.genSymOwner
  47. #if x.kind == skParam and x.owner.kind == skModule:
  48. # internalAssert c.config, false
  49. idTablePut(c.mapping, s, x)
  50. result.add newSymNode(x, if c.instLines: actual.info else: templ.info)
  51. else:
  52. result.add copyNode(c, templ, actual)
  53. of nkNone..nkIdent, nkType..nkNilLit: # atom
  54. result.add copyNode(c, templ, actual)
  55. of nkCommentStmt:
  56. # for the documentation generator we don't keep documentation comments
  57. # in the AST that would confuse it (bug #9432), but only if we are not in a
  58. # "declarative" context (bug #9235).
  59. if c.isDeclarative:
  60. var res = copyNode(c, templ, actual)
  61. for i in countup(0, sonsLen(templ) - 1):
  62. evalTemplateAux(templ.sons[i], actual, c, res)
  63. result.add res
  64. else:
  65. result.add newNodeI(nkEmpty, templ.info)
  66. else:
  67. var isDeclarative = false
  68. if templ.kind in {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef,
  69. nkMacroDef, nkTemplateDef, nkConverterDef, nkTypeSection,
  70. nkVarSection, nkLetSection, nkConstSection} and
  71. not c.isDeclarative:
  72. c.isDeclarative = true
  73. isDeclarative = true
  74. var res = copyNode(c, templ, actual)
  75. for i in countup(0, sonsLen(templ) - 1):
  76. evalTemplateAux(templ.sons[i], actual, c, res)
  77. result.add res
  78. if isDeclarative: c.isDeclarative = false
  79. const
  80. errWrongNumberOfArguments = "wrong number of arguments"
  81. errMissingGenericParamsForTemplate = "'$1' has unspecified generic parameters"
  82. errTemplateInstantiationTooNested = "template instantiation too nested"
  83. proc evalTemplateArgs(n: PNode, s: PSym; conf: ConfigRef; fromHlo: bool): PNode =
  84. # if the template has zero arguments, it can be called without ``()``
  85. # `n` is then a nkSym or something similar
  86. var totalParams = case n.kind
  87. of nkCallKinds: n.len-1
  88. else: 0
  89. var
  90. # XXX: Since immediate templates are not subject to the
  91. # standard sigmatching algorithm, they will have a number
  92. # of deficiencies when it comes to generic params:
  93. # Type dependencies between the parameters won't be honoured
  94. # and the bound generic symbols won't be resolvable within
  95. # their bodies. We could try to fix this, but it may be
  96. # wiser to just deprecate immediate templates and macros
  97. # now that we have working untyped parameters.
  98. genericParams = if sfImmediate in s.flags or fromHlo: 0
  99. else: s.ast[genericParamsPos].len
  100. expectedRegularParams = s.typ.len-1
  101. givenRegularParams = totalParams - genericParams
  102. if givenRegularParams < 0: givenRegularParams = 0
  103. if totalParams > expectedRegularParams + genericParams:
  104. globalError(conf, n.info, errWrongNumberOfArguments)
  105. if totalParams < genericParams:
  106. globalError(conf, n.info, errMissingGenericParamsForTemplate %
  107. n.renderTree)
  108. result = newNodeI(nkArgList, n.info)
  109. for i in 1 .. givenRegularParams:
  110. result.addSon n[i]
  111. # handle parameters with default values, which were
  112. # not supplied by the user
  113. for i in givenRegularParams+1 .. expectedRegularParams:
  114. let default = s.typ.n.sons[i].sym.ast
  115. if default.isNil or default.kind == nkEmpty:
  116. localError(conf, n.info, errWrongNumberOfArguments)
  117. addSon(result, newNodeI(nkEmpty, n.info))
  118. else:
  119. addSon(result, default.copyTree)
  120. # add any generic paramaters
  121. for i in 1 .. genericParams:
  122. result.addSon n.sons[givenRegularParams + i]
  123. # to prevent endless recursion in template instantiation
  124. const evalTemplateLimit* = 1000
  125. proc wrapInComesFrom*(info: TLineInfo; sym: PSym; res: PNode): PNode =
  126. when true:
  127. result = res
  128. result.info = info
  129. if result.kind in {nkStmtList, nkStmtListExpr} and result.len > 0:
  130. result.lastSon.info = info
  131. when false:
  132. # this hack is required to
  133. var x = result
  134. while x.kind == nkStmtListExpr: x = x.lastSon
  135. if x.kind in nkCallKinds:
  136. for i in 1..<x.len:
  137. if x[i].kind in nkCallKinds:
  138. x.sons[i].info = info
  139. else:
  140. result = newNodeI(nkStmtListExpr, info)
  141. var d = newNodeI(nkComesFrom, info)
  142. d.add newSymNode(sym, info)
  143. result.add d
  144. result.add res
  145. result.typ = res.typ
  146. proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym;
  147. conf: ConfigRef; fromHlo=false): PNode =
  148. inc(conf.evalTemplateCounter)
  149. if conf.evalTemplateCounter > evalTemplateLimit:
  150. globalError(conf, n.info, errTemplateInstantiationTooNested)
  151. result = n
  152. # replace each param by the corresponding node:
  153. var args = evalTemplateArgs(n, tmpl, conf, fromHlo)
  154. var ctx: TemplCtx
  155. ctx.owner = tmpl
  156. ctx.genSymOwner = genSymOwner
  157. ctx.config = conf
  158. initIdTable(ctx.mapping)
  159. let body = tmpl.getBody
  160. #echo "instantion of ", renderTree(body, {renderIds})
  161. if isAtom(body):
  162. result = newNodeI(nkPar, body.info)
  163. evalTemplateAux(body, args, ctx, result)
  164. if result.len == 1: result = result.sons[0]
  165. else:
  166. localError(conf, result.info, "illformed AST: " &
  167. renderTree(result, {renderNoComments}))
  168. else:
  169. result = copyNode(body)
  170. #ctx.instLines = body.kind notin {nkStmtList, nkStmtListExpr,
  171. # nkBlockStmt, nkBlockExpr}
  172. #if ctx.instLines: result.info = n.info
  173. for i in countup(0, safeLen(body) - 1):
  174. evalTemplateAux(body.sons[i], args, ctx, result)
  175. result.flags.incl nfFromTemplate
  176. result = wrapInComesFrom(n.info, tmpl, result)
  177. #if ctx.debugActive:
  178. # echo "instantion of ", renderTree(result, {renderIds})
  179. dec(conf.evalTemplateCounter)