summary refs log tree commit diff stats
path: root/compiler/evaltempl.nim
blob: 0f9220102b6cec9202062efbadd19d2a854b1068 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
parse/0: instruction: 1001
parse/0: instruction: 25
parse/0:   product: {name: "_", value: 0, type: 0, properties: ["_": "dummy"]}
parse/0:   product: {name: "12", value: 0, type: 1, properties: ["12": "integer"]}
after-brace/0: recipe main
after-brac
#
#
#           The Nim Compiler
#        (c) Copyright 2013 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## Template evaluation engine. Now hygienic.

import
  strutils, options, ast, astalgo, msgs, os, idents, wordrecg, renderer,
  lineinfos

type
  TemplCtx = object
    owner, genSymOwner: PSym
    instLines: bool   # use the instantiation lines numbers
    isDeclarative: bool
    mapping: TIdTable # every gensym'ed symbol needs to be mapped to some
                      # new symbol
    config: ConfigRef

proc copyNode(ctx: TemplCtx, a, b: PNode): PNode =
  result = copyNode(a)
  if ctx.instLines: result.info = b.info

proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) =
  template handleParam(param) =
    let x = param
    if x.kind == nkArgList:
      for y in items(x): result.add(y)
    else:
      result.add copyTree(x)

  case templ.kind
  of nkSym:
    var s = templ.sym
    if s.owner == nil or s.owner.id == c.owner.id:
      if s.kind == skParam and sfGenSym notin s.flags:
        handleParam actual.sons[s.position]
      elif (s.owner != nil) and (s.kind == skGenericParam or
           s.kind == skType and s.typ != nil and s.typ.kind == tyGenericParam):
        handleParam actual.sons[s.owner.typ.len + s.position - 1]
      else:
        internalAssert c.config, sfGenSym in s.flags or s.kind == skType
        var x = PSym(idTableGet(c.mapping, s))
        if x == nil:
          x = copySym(s)
          # sem'check needs to set the owner properly later, see bug #9476
          x.owner = nil # c.genSymOwner
          #if x.kind == skParam and x.owner.kind == skModule:
          #  internalAssert c.config, false
          idTablePut(c.mapping, s, x)
        result.add newSymNode(x, if c.instLines: actual.info else: templ.info)
    else:
      result.add copyNode(c, templ, actual)
  of nkNone..nkIdent, nkType..nkNilLit: # atom
    result.add copyNode(c, templ, actual)
  of nkCommentStmt:
    # for the documentation generator we don't keep documentation comments
    # in the AST that would confuse it (bug #9432), but only if we are not in a
    # "declarative" context (bug #9235).
    if c.isDeclarative:
      var res = copyNode(c, templ, actual)
      for i in countup(0, sonsLen(templ) - 1):
        evalTemplateAux(templ.sons[i], actual, c, res)
      result.add res
    else:
      result.add newNodeI(nkEmpty, templ.info)
  else:
    var isDeclarative = false
    if templ.kind in {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef,
                      nkMacroDef, nkTemplateDef, nkConverterDef, nkTypeSection,
                      nkVarSection, nkLetSection, nkConstSection} and
        not c.isDeclarative:
      c.isDeclarative = true
      isDeclarative = true
    var res = copyNode(c, templ, actual)
    for i in countup(0, sonsLen(templ) - 1):
      evalTemplateAux(templ.sons[i], actual, c, res)
    result.add res
    if isDeclarative: c.isDeclarative = false

const
  errWrongNumberOfArguments = "wrong number of arguments"
  errMissingGenericParamsForTemplate = "'$1' has unspecified generic parameters"
  errTemplateInstantiationTooNested = "template instantiation too nested"

proc evalTemplateArgs(n: PNode, s: PSym; conf: ConfigRef; fromHlo: bool): PNode =
  # if the template has zero arguments, it can be called without ``()``
  # `n` is then a nkSym or something similar
  var totalParams = case n.kind
    of nkCallKinds: n.len-1
    else: 0

  var
    # XXX: Since immediate templates are not subject to the
    # standard sigmatching algorithm, they will have a number
    # of deficiencies when it comes to generic params:
    # Type dependencies between the parameters won't be honoured
    # and the bound generic symbols won't be resolvable within
    # their bodies. We could try to fix this, but it may be
    # wiser to just deprecate immediate templates and macros
    # now that we have working untyped parameters.
    genericParams = if sfImmediate in s.flags or fromHlo: 0
                    else: s.ast[genericParamsPos].len
    expectedRegularParams = s.typ.len-1
    givenRegularParams = totalParams - genericParams
  if givenRegularParams < 0: givenRegularParams = 0

  if totalParams > expectedRegularParams + genericParams:
    globalError(conf, n.info, errWrongNumberOfArguments)

  if totalParams < genericParams:
    globalError(conf, n.info, errMissingGenericParamsForTemplate %
                n.renderTree)

  result = newNodeI(nkArgList, n.info)
  for i in 1 .. givenRegularParams:
    result.addSon n[i]

  # handle parameters with default values, which were
  # not supplied by the user
  for i in givenRegularParams+1 .. expectedRegularParams:
    let default = s.typ.n.sons[i].sym.ast
    if default.isNil or default.kind == nkEmpty:
      localError(conf, n.info, errWrongNumberOfArguments)
      addSon(result, newNodeI(nkEmpty, n.info))
    else:
      addSon(result, default.copyTree)

  # add any generic paramaters
  for i in 1 .. genericParams:
    result.addSon n.sons[givenRegularParams + i]

# to prevent endless recursion in template instantiation
const evalTemplateLimit* = 1000

proc wrapInComesFrom*(info: TLineInfo; sym: PSym; res: PNode): PNode =
  when true:
    result = res
    result.info = info
    if result.kind in {nkStmtList, nkStmtListExpr} and result.len > 0:
      result.lastSon.info = info
    when false:
      # this hack is required to
      var x = result
      while x.kind == nkStmtListExpr: x = x.lastSon
      if x.kind in nkCallKinds:
        for i in 1..<x.len:
          if x[i].kind in nkCallKinds:
            x.sons[i].info = info
  else:
    result = newNodeI(nkStmtListExpr, info)
    var d = newNodeI(nkComesFrom, info)
    d.add newSymNode(sym, info)
    result.add d
    result.add res
    result.typ = res.typ

proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym;
                   conf: ConfigRef; fromHlo=false): PNode =
  inc(conf.evalTemplateCounter)
  if conf.evalTemplateCounter > evalTemplateLimit:
    globalError(conf, n.info, errTemplateInstantiationTooNested)
    result = n

  # replace each param by the corresponding node:
  var args = evalTemplateArgs(n, tmpl, conf, fromHlo)
  var ctx: TemplCtx
  ctx.owner = tmpl
  ctx.genSymOwner = genSymOwner
  ctx.config = conf
  initIdTable(ctx.mapping)

  let body = tmpl.getBody
  #echo "instantion of ", renderTree(body, {renderIds})
  if isAtom(body):
    result = newNodeI(nkPar, body.info)
    evalTemplateAux(body, args, ctx, result)
    if result.len == 1: result = result.sons[0]
    else:
      localError(conf, result.info, "illformed AST: " &
                  renderTree(result, {renderNoComments}))
  else:
    result = copyNode(body)
    #ctx.instLines = body.kind notin {nkStmtList, nkStmtListExpr,
    #                                 nkBlockStmt, nkBlockExpr}
    #if ctx.instLines: result.info = n.info
    for i in countup(0, safeLen(body) - 1):
      evalTemplateAux(body.sons[i], args, ctx, result)
  result.flags.incl nfFromTemplate
  result = wrapInComesFrom(n.info, tmpl, result)
  #if ctx.debugActive:
  #  echo "instantion of ", renderTree(result, {renderIds})
  dec(conf.evalTemplateCounter)