# # # 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, renderer, lineinfos, idents 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 ic: IdentCache instID: int idgen: IdGenerator 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 and s.kind == skParam) or s.owner == c.owner: if s.kind == skParam and {sfGenSym, sfTemplateParam} * s.flags == {sfTemplateParam}: handleParam actual[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[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, nextSymId(c.idgen)) # 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) if sfGenSym in s.flags: result.add newIdentNode(getIdent(c.ic, x.name.s & "`gensym" & $c.instID), if c.instLines: actual.info else: templ.info) else: 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 0.. 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.add 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[i].sym.ast if default.isNil or default.kind == nkEmpty: localError(conf, n.info, errWrongNumberOfArguments) result.add newNodeI(nkEmpty, n.info) else: result.add default.copyTree # add any generic parameters for i in 1..genericParams: result.add n[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.. 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 ctx.ic = ic initIdTable(ctx.mapping) ctx.instID = instID[] ctx.idgen = idgen let body = tmpl.ast[bodyPos] #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[0] else: localError(conf, result.info, "illformed AST: " & renderTree(result, {renderNoComments})) else: result = copyNode(body) ctx.instLines = sfCallsite in tmpl.flags if ctx.instLines: result.info = n.info for i in 0..