evaltempl.nim 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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 options, ast, astalgo, msgs, renderer, lineinfos, idents, trees
  11. import std/strutils
  12. type
  13. TemplCtx = object
  14. owner, genSymOwner: PSym
  15. instLines: bool # use the instantiation lines numbers
  16. isDeclarative: bool
  17. mapping: SymMapping # every gensym'ed symbol needs to be mapped to some
  18. # new symbol
  19. config: ConfigRef
  20. ic: IdentCache
  21. instID: int
  22. idgen: IdGenerator
  23. proc copyNode(ctx: TemplCtx, a, b: PNode): PNode =
  24. result = copyNode(a)
  25. if ctx.instLines: setInfoRecursive(result, b.info)
  26. proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) =
  27. template handleParam(param) =
  28. let x = param
  29. if x.kind == nkArgList:
  30. for y in items(x): result.add(y)
  31. elif nfDefaultRefsParam in x.flags:
  32. # value of default param needs to be evaluated like template body
  33. # if it contains other template params
  34. var res: PNode
  35. if isAtom(x):
  36. res = newNodeI(nkPar, x.info)
  37. evalTemplateAux(x, actual, c, res)
  38. if res.len == 1: res = res[0]
  39. else:
  40. res = copyNode(x)
  41. for i in 0..<x.safeLen:
  42. evalTemplateAux(x[i], actual, c, res)
  43. result.add res
  44. else:
  45. result.add copyTree(x)
  46. case templ.kind
  47. of nkSym:
  48. var s = templ.sym
  49. if (s.owner == nil and s.kind == skParam) or s.owner == c.owner:
  50. if s.kind == skParam and {sfGenSym, sfTemplateParam} * s.flags == {sfTemplateParam}:
  51. handleParam actual[s.position]
  52. elif (s.owner != nil) and (s.kind == skGenericParam or
  53. s.kind == skType and s.typ != nil and s.typ.kind == tyGenericParam):
  54. handleParam actual[s.owner.typ.signatureLen + s.position - 1]
  55. else:
  56. internalAssert c.config, sfGenSym in s.flags or s.kind == skType
  57. var x = idTableGet(c.mapping, s)
  58. if x == nil:
  59. x = copySym(s, c.idgen)
  60. # sem'check needs to set the owner properly later, see bug #9476
  61. setOwner(x, nil) # c.genSymOwner
  62. #if x.kind == skParam and x.owner.kind == skModule:
  63. # internalAssert c.config, false
  64. idTablePut(c.mapping, s, x)
  65. if sfGenSym in s.flags:
  66. # TODO: getIdent(c.ic, "`" & x.name.s & "`gensym" & $c.instID)
  67. result.add newIdentNode(getIdent(c.ic, x.name.s & "`gensym" & $c.instID),
  68. if c.instLines: actual.info else: templ.info)
  69. else:
  70. result.add newSymNode(x, if c.instLines: actual.info else: templ.info)
  71. else:
  72. result.add copyNode(c, templ, actual)
  73. of nkNone..nkIdent, nkType..nkNilLit: # atom
  74. result.add copyNode(c, templ, actual)
  75. of nkCommentStmt:
  76. # for the documentation generator we don't keep documentation comments
  77. # in the AST that would confuse it (bug #9432), but only if we are not in a
  78. # "declarative" context (bug #9235).
  79. if c.isDeclarative:
  80. var res = copyNode(c, templ, actual)
  81. for i in 0..<templ.len:
  82. evalTemplateAux(templ[i], actual, c, res)
  83. result.add res
  84. else:
  85. result.add newNodeI(nkEmpty, templ.info)
  86. else:
  87. var isDeclarative = false
  88. if templ.kind in {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef,
  89. nkMacroDef, nkTemplateDef, nkConverterDef, nkTypeSection,
  90. nkVarSection, nkLetSection, nkConstSection} and
  91. not c.isDeclarative:
  92. c.isDeclarative = true
  93. isDeclarative = true
  94. if (not c.isDeclarative) and templ.kind in nkCallKinds and isRunnableExamples(templ[0]):
  95. # fixes bug #16993, bug #18054
  96. discard
  97. else:
  98. var res = copyNode(c, templ, actual)
  99. for i in 0..<templ.len:
  100. evalTemplateAux(templ[i], actual, c, res)
  101. result.add res
  102. if isDeclarative: c.isDeclarative = false
  103. const
  104. errWrongNumberOfArguments = "wrong number of arguments"
  105. errMissingGenericParamsForTemplate = "'$1' has unspecified generic parameters"
  106. errTemplateInstantiationTooNested = "template instantiation too nested"
  107. proc evalTemplateArgs(n: PNode, s: PSym; conf: ConfigRef; fromHlo: bool): PNode =
  108. # if the template has zero arguments, it can be called without ``()``
  109. # `n` is then a nkSym or something similar
  110. var totalParams = case n.kind
  111. of nkCallKinds: n.len-1
  112. else: 0
  113. var
  114. # XXX: Since immediate templates are not subject to the
  115. # standard sigmatching algorithm, they will have a number
  116. # of deficiencies when it comes to generic params:
  117. # Type dependencies between the parameters won't be honoured
  118. # and the bound generic symbols won't be resolvable within
  119. # their bodies. We could try to fix this, but it may be
  120. # wiser to just deprecate immediate templates and macros
  121. # now that we have working untyped parameters.
  122. genericParams = if fromHlo: 0
  123. else: s.ast[genericParamsPos].len
  124. expectedRegularParams = s.typ.paramsLen
  125. givenRegularParams = totalParams - genericParams
  126. if givenRegularParams < 0: givenRegularParams = 0
  127. if totalParams > expectedRegularParams + genericParams:
  128. globalError(conf, n.info, errWrongNumberOfArguments)
  129. if totalParams < genericParams:
  130. globalError(conf, n.info, errMissingGenericParamsForTemplate %
  131. n.renderTree)
  132. result = newNodeI(nkArgList, n.info)
  133. for i in 1..givenRegularParams:
  134. result.add n[i]
  135. # handle parameters with default values, which were
  136. # not supplied by the user
  137. for i in givenRegularParams+1..expectedRegularParams:
  138. let default = s.typ.n[i].sym.ast
  139. if default.isNil or default.kind == nkEmpty:
  140. localError(conf, n.info, errWrongNumberOfArguments)
  141. result.add newNodeI(nkEmpty, n.info)
  142. else:
  143. result.add default.copyTree
  144. # add any generic parameters
  145. for i in 1..genericParams:
  146. result.add n[givenRegularParams + i]
  147. # to prevent endless recursion in template instantiation
  148. const evalTemplateLimit* = 1000
  149. proc wrapInComesFrom*(info: TLineInfo; sym: PSym; res: PNode): PNode =
  150. when true:
  151. result = res
  152. result.info = info
  153. if result.kind in {nkStmtList, nkStmtListExpr} and result.len > 0:
  154. result.lastSon.info = info
  155. when false:
  156. # this hack is required to
  157. var x = result
  158. while x.kind == nkStmtListExpr: x = x.lastSon
  159. if x.kind in nkCallKinds:
  160. for i in 1..<x.len:
  161. if x[i].kind in nkCallKinds:
  162. x[i].info = info
  163. else:
  164. result = newNodeI(nkStmtListExpr, info)
  165. var d = newNodeI(nkComesFrom, info)
  166. d.add newSymNode(sym, info)
  167. result.add d
  168. result.add res
  169. result.typ() = res.typ
  170. proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym;
  171. conf: ConfigRef;
  172. ic: IdentCache; instID: ref int;
  173. idgen: IdGenerator;
  174. fromHlo=false): PNode =
  175. inc(conf.evalTemplateCounter)
  176. if conf.evalTemplateCounter > evalTemplateLimit:
  177. globalError(conf, n.info, errTemplateInstantiationTooNested)
  178. result = n
  179. # replace each param by the corresponding node:
  180. var args = evalTemplateArgs(n, tmpl, conf, fromHlo)
  181. var ctx = TemplCtx(owner: tmpl,
  182. genSymOwner: genSymOwner,
  183. config: conf,
  184. ic: ic,
  185. mapping: initSymMapping(),
  186. instID: instID[],
  187. idgen: idgen
  188. )
  189. let body = tmpl.ast[bodyPos]
  190. #echo "instantion of ", renderTree(body, {renderIds})
  191. if isAtom(body):
  192. result = newNodeI(nkPar, body.info)
  193. evalTemplateAux(body, args, ctx, result)
  194. if result.len == 1: result = result[0]
  195. else:
  196. localError(conf, result.info, "illformed AST: " &
  197. renderTree(result, {renderNoComments}))
  198. else:
  199. result = copyNode(body)
  200. ctx.instLines = sfCallsite in tmpl.flags
  201. if ctx.instLines:
  202. setInfoRecursive(result, n.info)
  203. for i in 0..<body.safeLen:
  204. evalTemplateAux(body[i], args, ctx, result)
  205. result.flags.incl nfFromTemplate
  206. result = wrapInComesFrom(n.info, tmpl, result)
  207. #if ctx.debugActive:
  208. # echo "instantion of ", renderTree(result, {renderIds})
  209. dec(conf.evalTemplateCounter)
  210. # The instID must be unique for every template instantiation, so we increment it here
  211. inc instID[]