diff options
Diffstat (limited to 'compiler/evaltempl.nim')
-rw-r--r-- | compiler/evaltempl.nim | 201 |
1 files changed, 135 insertions, 66 deletions
diff --git a/compiler/evaltempl.nim b/compiler/evaltempl.nim index f088afcdb..77c136d63 100644 --- a/compiler/evaltempl.nim +++ b/compiler/evaltempl.nim @@ -9,61 +9,114 @@ ## Template evaluation engine. Now hygienic. -import - strutils, options, ast, astalgo, msgs, os, idents, wordrecg, renderer, - rodread +import options, ast, astalgo, msgs, renderer, lineinfos, idents, trees +import std/strutils type - TemplCtx {.pure, final.} = object + TemplCtx = object owner, genSymOwner: PSym instLines: bool # use the instantiation lines numbers - mapping: TIdTable # every gensym'ed symbol needs to be mapped to some - # new symbol + isDeclarative: bool + mapping: SymMapping # 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 + if ctx.instLines: setInfoRecursive(result, 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) + elif nfDefaultRefsParam in x.flags: + # value of default param needs to be evaluated like template body + # if it contains other template params + var res: PNode + if isAtom(x): + res = newNodeI(nkPar, x.info) + evalTemplateAux(x, actual, c, res) + if res.len == 1: res = res[0] + else: + res = copyNode(x) + for i in 0..<x.safeLen: + evalTemplateAux(x[i], actual, c, res) + result.add res else: result.add copyTree(x) case templ.kind of nkSym: var s = templ.sym - if s.owner.id == c.owner.id: - if s.kind == skParam and sfGenSym notin s.flags: - handleParam actual.sons[s.position] - elif 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] + 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.signatureLen + s.position - 1] else: - internalAssert sfGenSym in s.flags - var x = PSym(idTableGet(c.mapping, s)) + internalAssert c.config, sfGenSym in s.flags or s.kind == skType + var x = idTableGet(c.mapping, s) if x == nil: - x = copySym(s, false) - x.owner = c.genSymOwner + x = copySym(s, 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) - result.add newSymNode(x, if c.instLines: actual.info else: templ.info) + if sfGenSym in s.flags: + # TODO: getIdent(c.ic, "`" & x.name.s & "`gensym" & $c.instID) + 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..<templ.len: + evalTemplateAux(templ[i], actual, c, res) + result.add res + else: + result.add newNodeI(nkEmpty, templ.info) else: - var res = copyNode(c, templ, actual) - for i in countup(0, sonsLen(templ) - 1): - evalTemplateAux(templ.sons[i], actual, c, res) - result.add res - -proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode = + 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 + if (not c.isDeclarative) and templ.kind in nkCallKinds and isRunnableExamples(templ[0]): + # fixes bug #16993, bug #18054 + discard + else: + var res = copyNode(c, templ, actual) + for i in 0..<templ.len: + evalTemplateAux(templ[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 nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit: n.len-1 + of nkCallKinds: n.len-1 else: 0 var @@ -75,41 +128,41 @@ proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode = # 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 + genericParams = if fromHlo: 0 else: s.ast[genericParamsPos].len - expectedRegularParams = <s.typ.len + expectedRegularParams = s.typ.paramsLen givenRegularParams = totalParams - genericParams if givenRegularParams < 0: givenRegularParams = 0 if totalParams > expectedRegularParams + genericParams: - globalError(n.info, errWrongNumberOfArguments) + globalError(conf, n.info, errWrongNumberOfArguments) if totalParams < genericParams: - globalError(n.info, errMissingGenericParamsForTemplate, + globalError(conf, n.info, errMissingGenericParamsForTemplate % n.renderTree) result = newNodeI(nkArgList, n.info) - for i in 1 .. givenRegularParams: - result.addSon n[i] + 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.sons[i].sym.ast + for i in givenRegularParams+1..expectedRegularParams: + let default = s.typ.n[i].sym.ast if default.isNil or default.kind == nkEmpty: - localError(n.info, errWrongNumberOfArguments) - addSon(result, ast.emptyNode) + localError(conf, n.info, errWrongNumberOfArguments) + result.add newNodeI(nkEmpty, n.info) else: - addSon(result, default.copyTree) + result.add default.copyTree - # add any generic paramaters - for i in 1 .. genericParams: - result.addSon n.sons[givenRegularParams + i] + # add any generic parameters + for i in 1..genericParams: + result.add n[givenRegularParams + i] -var evalTemplateCounter* = 0 - # to prevent endless recursion in templates instantiation +# to prevent endless recursion in template instantiation +const evalTemplateLimit* = 1000 -proc wrapInComesFrom*(info: TLineInfo; res: PNode): PNode = +proc wrapInComesFrom*(info: TLineInfo; sym: PSym; res: PNode): PNode = when true: result = res result.info = info @@ -122,40 +175,56 @@ proc wrapInComesFrom*(info: TLineInfo; res: PNode): PNode = if x.kind in nkCallKinds: for i in 1..<x.len: if x[i].kind in nkCallKinds: - x.sons[i].info = info + x[i].info = info else: - result = newNodeI(nkPar, info) + result = newNodeI(nkStmtListExpr, info) + var d = newNodeI(nkComesFrom, info) + d.add newSymNode(sym, info) + result.add d result.add res - -proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym; fromHlo=false): PNode = - inc(evalTemplateCounter) - if evalTemplateCounter > 100: - globalError(n.info, errTemplateInstantiationTooNested) + result.typ = res.typ + +proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym; + conf: ConfigRef; + ic: IdentCache; instID: ref int; + idgen: IdGenerator; + 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, fromHlo) - var ctx: TemplCtx - ctx.owner = tmpl - ctx.genSymOwner = genSymOwner - initIdTable(ctx.mapping) - - let body = tmpl.getBody + var args = evalTemplateArgs(n, tmpl, conf, fromHlo) + var ctx = TemplCtx(owner: tmpl, + genSymOwner: genSymOwner, + config: conf, + ic: ic, + mapping: initSymMapping(), + instID: instID[], + 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.sons[0] + if result.len == 1: result = result[0] else: - localError(result.info, errIllFormedAstX, + 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) + ctx.instLines = sfCallsite in tmpl.flags + if ctx.instLines: + setInfoRecursive(result, n.info) + for i in 0..<body.safeLen: + evalTemplateAux(body[i], args, ctx, result) result.flags.incl nfFromTemplate - result = wrapInComesFrom(n.info, result) - dec(evalTemplateCounter) - + result = wrapInComesFrom(n.info, tmpl, result) + #if ctx.debugActive: + # echo "instantion of ", renderTree(result, {renderIds}) + dec(conf.evalTemplateCounter) + # The instID must be unique for every template instantiation, so we increment it here + inc instID[] |