evaltempl.nim 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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. rodread
  13. type
  14. TemplCtx {.pure, final.} = object
  15. owner, genSymOwner: PSym
  16. instLines: bool # use the instantiation lines numbers
  17. mapping: TIdTable # every gensym'ed symbol needs to be mapped to some
  18. # new symbol
  19. proc copyNode(ctx: TemplCtx, a, b: PNode): PNode =
  20. result = copyNode(a)
  21. if ctx.instLines: result.info = b.info
  22. proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) =
  23. template handleParam(param) =
  24. let x = param
  25. if x.kind == nkArgList:
  26. for y in items(x): result.add(y)
  27. else:
  28. result.add copyTree(x)
  29. case templ.kind
  30. of nkSym:
  31. var s = templ.sym
  32. if s.owner.id == c.owner.id:
  33. if s.kind == skParam and sfGenSym notin s.flags:
  34. handleParam actual.sons[s.position]
  35. elif s.kind == skGenericParam or
  36. s.kind == skType and s.typ != nil and s.typ.kind == tyGenericParam:
  37. handleParam actual.sons[s.owner.typ.len + s.position - 1]
  38. else:
  39. internalAssert sfGenSym in s.flags
  40. var x = PSym(idTableGet(c.mapping, s))
  41. if x == nil:
  42. x = copySym(s, false)
  43. x.owner = c.genSymOwner
  44. idTablePut(c.mapping, s, x)
  45. result.add newSymNode(x, if c.instLines: actual.info else: templ.info)
  46. else:
  47. result.add copyNode(c, templ, actual)
  48. of nkNone..nkIdent, nkType..nkNilLit: # atom
  49. result.add copyNode(c, templ, actual)
  50. else:
  51. var res = copyNode(c, templ, actual)
  52. for i in countup(0, sonsLen(templ) - 1):
  53. evalTemplateAux(templ.sons[i], actual, c, res)
  54. result.add res
  55. proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode =
  56. # if the template has zero arguments, it can be called without ``()``
  57. # `n` is then a nkSym or something similar
  58. var totalParams = case n.kind
  59. of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit: n.len-1
  60. else: 0
  61. var
  62. # XXX: Since immediate templates are not subject to the
  63. # standard sigmatching algorithm, they will have a number
  64. # of deficiencies when it comes to generic params:
  65. # Type dependencies between the parameters won't be honoured
  66. # and the bound generic symbols won't be resolvable within
  67. # their bodies. We could try to fix this, but it may be
  68. # wiser to just deprecate immediate templates and macros
  69. # now that we have working untyped parameters.
  70. genericParams = if sfImmediate in s.flags or fromHlo: 0
  71. else: s.ast[genericParamsPos].len
  72. expectedRegularParams = <s.typ.len
  73. givenRegularParams = totalParams - genericParams
  74. if givenRegularParams < 0: givenRegularParams = 0
  75. if totalParams > expectedRegularParams + genericParams:
  76. globalError(n.info, errWrongNumberOfArguments)
  77. if totalParams < genericParams:
  78. globalError(n.info, errMissingGenericParamsForTemplate,
  79. n.renderTree)
  80. result = newNodeI(nkArgList, n.info)
  81. for i in 1 .. givenRegularParams:
  82. result.addSon n[i]
  83. # handle parameters with default values, which were
  84. # not supplied by the user
  85. for i in givenRegularParams+1 .. expectedRegularParams:
  86. let default = s.typ.n.sons[i].sym.ast
  87. if default.isNil or default.kind == nkEmpty:
  88. localError(n.info, errWrongNumberOfArguments)
  89. addSon(result, ast.emptyNode)
  90. else:
  91. addSon(result, default.copyTree)
  92. # add any generic paramaters
  93. for i in 1 .. genericParams:
  94. result.addSon n.sons[givenRegularParams + i]
  95. var evalTemplateCounter* = 0
  96. # to prevent endless recursion in templates instantiation
  97. proc wrapInComesFrom*(info: TLineInfo; res: PNode): PNode =
  98. when true:
  99. result = res
  100. result.info = info
  101. if result.kind in {nkStmtList, nkStmtListExpr} and result.len > 0:
  102. result.lastSon.info = info
  103. when false:
  104. # this hack is required to
  105. var x = result
  106. while x.kind == nkStmtListExpr: x = x.lastSon
  107. if x.kind in nkCallKinds:
  108. for i in 1..<x.len:
  109. if x[i].kind in nkCallKinds:
  110. x.sons[i].info = info
  111. else:
  112. result = newNodeI(nkPar, info)
  113. result.add res
  114. proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym; fromHlo=false): PNode =
  115. inc(evalTemplateCounter)
  116. if evalTemplateCounter > 100:
  117. globalError(n.info, errTemplateInstantiationTooNested)
  118. result = n
  119. # replace each param by the corresponding node:
  120. var args = evalTemplateArgs(n, tmpl, fromHlo)
  121. var ctx: TemplCtx
  122. ctx.owner = tmpl
  123. ctx.genSymOwner = genSymOwner
  124. initIdTable(ctx.mapping)
  125. let body = tmpl.getBody
  126. if isAtom(body):
  127. result = newNodeI(nkPar, body.info)
  128. evalTemplateAux(body, args, ctx, result)
  129. if result.len == 1: result = result.sons[0]
  130. else:
  131. localError(result.info, errIllFormedAstX,
  132. renderTree(result, {renderNoComments}))
  133. else:
  134. result = copyNode(body)
  135. #ctx.instLines = body.kind notin {nkStmtList, nkStmtListExpr,
  136. # nkBlockStmt, nkBlockExpr}
  137. #if ctx.instLines: result.info = n.info
  138. for i in countup(0, safeLen(body) - 1):
  139. evalTemplateAux(body.sons[i], args, ctx, result)
  140. result.flags.incl nfFromTemplate
  141. result = wrapInComesFrom(n.info, result)
  142. dec(evalTemplateCounter)