first steps to make templates hygienic
This commit is contained in:
parent
0cac8d9b6f
commit
5e15dec175
23 changed files with 486 additions and 187 deletions
|
|
@ -9,6 +9,37 @@
|
|||
|
||||
# included from sem.nim
|
||||
|
||||
discard """
|
||||
hygienic templates:
|
||||
|
||||
template `||` (a, b: expr): expr =
|
||||
let aa = a
|
||||
(if aa: aa else: b)
|
||||
|
||||
var
|
||||
a, b: T
|
||||
|
||||
a || b || a
|
||||
|
||||
Each evaluation context has to be different and we need to perform
|
||||
some form of preliminary symbol lookup in template definitions. Hygiene is
|
||||
a way to achieve lexical scoping at compile time.
|
||||
"""
|
||||
|
||||
type
|
||||
TSymBinding = enum
|
||||
spNone, spGenSym, spInject
|
||||
|
||||
proc symBinding(n: PNode): TSymBinding =
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
var it = n.sons[i]
|
||||
var key = if it.kind == nkExprColonExpr: it.sons[0] else: it
|
||||
if key.kind == nkIdent:
|
||||
case whichKeyword(key.ident)
|
||||
of wGenSym: return spGenSym
|
||||
of wInject: return spInject
|
||||
else: nil
|
||||
|
||||
proc symChoice(c: PContext, n: PNode, s: PSym): PNode =
|
||||
var
|
||||
a: PSym
|
||||
|
|
@ -20,17 +51,16 @@ proc symChoice(c: PContext, n: PNode, s: PSym): PNode =
|
|||
inc(i)
|
||||
if i > 1: break
|
||||
if i <= 1:
|
||||
result = newSymNode(s)
|
||||
result.info = n.info
|
||||
result = newSymNode(s, n.info)
|
||||
markUsed(n, s)
|
||||
else:
|
||||
else:
|
||||
# semantic checking requires a type; ``fitNode`` deals with it
|
||||
# appropriately
|
||||
result = newNodeIT(nkSymChoice, n.info, newTypeS(tyNone, c))
|
||||
a = initOverloadIter(o, c, n)
|
||||
while a != nil:
|
||||
incl(a.flags, sfUsed)
|
||||
addSon(result, newSymNode(a))
|
||||
addSon(result, newSymNode(a, n.info))
|
||||
a = nextOverloadIter(o, c, n)
|
||||
|
||||
proc semBindStmt(c: PContext, n: PNode, toBind: var TIntSet): PNode =
|
||||
|
|
@ -45,37 +75,217 @@ proc semBindStmt(c: PContext, n: PNode, toBind: var TIntSet): PNode =
|
|||
else:
|
||||
illFormedAst(a)
|
||||
result = newNodeI(nkEmpty, n.info)
|
||||
|
||||
proc resolveTemplateParams(c: PContext, n: PNode, owner: PSym,
|
||||
toBind: var TIntSet): PNode =
|
||||
var s: PSym
|
||||
|
||||
proc replaceIdentBySym(n: var PNode, s: PNode) =
|
||||
case n.kind
|
||||
of nkPostfix: replaceIdentBySym(n.sons[1], s)
|
||||
of nkPragmaExpr: replaceIdentBySym(n.sons[0], s)
|
||||
of nkIdent, nkAccQuoted, nkSym: n = s
|
||||
else: illFormedAst(n)
|
||||
|
||||
type
|
||||
TemplCtx {.pure, final.} = object
|
||||
c: PContext
|
||||
toBind: TIntSet
|
||||
owner: PSym
|
||||
|
||||
proc getIdentNode(c: var TemplCtx, n: PNode): PNode =
|
||||
case n.kind
|
||||
of nkPostfix: result = getIdentNode(c, n.sons[1])
|
||||
of nkPragmaExpr: result = getIdentNode(c, n.sons[0])
|
||||
of nkIdent:
|
||||
result = n
|
||||
let s = QualifiedLookUp(c, n, {})
|
||||
let s = QualifiedLookUp(c.c, n, {})
|
||||
if s != nil:
|
||||
if s.owner == owner and s.kind == skParam:
|
||||
result = newSymNode(s)
|
||||
result.info = n.info
|
||||
elif Contains(toBind, s.id):
|
||||
result = symChoice(c, n, s)
|
||||
|
||||
of nkEmpty, nkSym..nkNilLit: # atom
|
||||
if s.owner == c.owner and s.kind == skParam:
|
||||
result = newSymNode(s, n.info)
|
||||
of nkAccQuoted, nkSym: result = n
|
||||
else:
|
||||
illFormedAst(n)
|
||||
result = n
|
||||
|
||||
proc isTemplParam(n: PNode): bool {.inline.} =
|
||||
result = n.kind == nkSym and n.sym.kind == skParam and
|
||||
n.sym.owner.kind == skTemplate
|
||||
|
||||
proc semTemplBody(c: var TemplCtx, n: PNode): PNode
|
||||
|
||||
proc openScope(c: var TemplCtx) = openScope(c.c.tab)
|
||||
proc closeScope(c: var TemplCtx) = closeScope(c.c.tab)
|
||||
|
||||
proc semTemplBodyScope(c: var TemplCtx, n: PNode): PNode =
|
||||
openScope(c)
|
||||
result = semTemplBody(c, n)
|
||||
closeScope(c)
|
||||
|
||||
proc newGenSym(kind: TSymKind, n: PNode, c: var TemplCtx): PSym =
|
||||
result = newSym(kind, considerAcc(n), c.owner)
|
||||
incl(result.flags, sfGenSym)
|
||||
incl(result.flags, sfShadowed)
|
||||
|
||||
proc addLocalDecl(c: var TemplCtx, n: var PNode, k: TSymKind) =
|
||||
# locals default to 'gensym':
|
||||
if n.kind != nkPragmaExpr or symBinding(n.sons[1]) != spInject:
|
||||
let ident = getIdentNode(c, n)
|
||||
if not isTemplParam(ident):
|
||||
let local = newGenSym(k, ident, c)
|
||||
addPrelimDecl(c.c, local)
|
||||
replaceIdentBySym(n, newSymNode(local, n.info))
|
||||
else:
|
||||
replaceIdentBySym(n, ident)
|
||||
else:
|
||||
n = semTemplBody(c, n)
|
||||
|
||||
proc semRoutineInTemplBody(c: var TemplCtx, n: PNode, k: TSymKind): PNode =
|
||||
result = n
|
||||
checkSonsLen(n, bodyPos + 1)
|
||||
# routines default to 'inject':
|
||||
if n.kind notin nkLambdaKinds and symBinding(n.sons[pragmasPos]) == spGenSym:
|
||||
let ident = getIdentNode(c, n.sons[namePos])
|
||||
if not isTemplParam(ident):
|
||||
let s = newGenSym(k, ident, c)
|
||||
addPrelimDecl(c.c, s)
|
||||
n.sons[namePos] = newSymNode(s, n.sons[namePos].info)
|
||||
else:
|
||||
n.sons[namePos] = ident
|
||||
else:
|
||||
n.sons[namePos] = semTemplBody(c, n.sons[namePos])
|
||||
openScope(c)
|
||||
n.sons[genericParamsPos] = semTemplBody(c, n.sons[genericParamsPos])
|
||||
n.sons[paramsPos] = semTemplBody(c, n.sons[paramsPos])
|
||||
n.sons[pragmasPos] = semTemplBody(c, n.sons[pragmasPos])
|
||||
n.sons[bodyPos] = semTemplBodyScope(c, n.sons[bodyPos])
|
||||
closeScope(c)
|
||||
|
||||
proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
|
||||
result = n
|
||||
case n.kind
|
||||
of nkIdent:
|
||||
let s = QualifiedLookUp(c.c, n, {})
|
||||
if s != nil:
|
||||
if s.owner == c.owner and s.kind == skParam:
|
||||
result = newSymNode(s, n.info)
|
||||
elif Contains(c.toBind, s.id):
|
||||
result = symChoice(c.c, n, s)
|
||||
elif s.owner == c.owner:
|
||||
InternalAssert sfGenSym in s.flags
|
||||
incl(s.flags, sfUsed)
|
||||
result = newSymNode(s, n.info)
|
||||
of nkBind:
|
||||
result = resolveTemplateParams(c, n.sons[0], owner, toBind)
|
||||
result = semTemplBody(c, n.sons[0])
|
||||
of nkBindStmt:
|
||||
result = semBindStmt(c, n, toBind)
|
||||
result = semBindStmt(c.c, n, c.toBind)
|
||||
of nkEmpty, nkSym..nkNilLit:
|
||||
nil
|
||||
of nkIfStmt:
|
||||
for i in countup(0, sonsLen(n)-1):
|
||||
n.sons[i] = semTemplBodyScope(c, n.sons[i])
|
||||
of nkWhileStmt:
|
||||
openScope(c)
|
||||
for i in countup(0, sonsLen(n)-1):
|
||||
n.sons[i] = semTemplBody(c, n.sons[i])
|
||||
closeScope(c)
|
||||
of nkCaseStmt:
|
||||
openScope(c)
|
||||
n.sons[0] = semTemplBody(c, n.sons[0])
|
||||
for i in countup(1, sonsLen(n)-1):
|
||||
var a = n.sons[i]
|
||||
checkMinSonsLen(a, 1)
|
||||
var L = sonsLen(a)
|
||||
for j in countup(0, L-2):
|
||||
a.sons[j] = semTemplBody(c, a.sons[j])
|
||||
a.sons[L-1] = semTemplBodyScope(c, a.sons[L-1])
|
||||
closeScope(c)
|
||||
of nkForStmt, nkParForStmt:
|
||||
var L = sonsLen(n)
|
||||
openScope(c)
|
||||
n.sons[L-2] = semTemplBody(c, n.sons[L-2])
|
||||
for i in countup(0, L - 3):
|
||||
addLocalDecl(c, n.sons[i], skForVar)
|
||||
n.sons[L-1] = semTemplBody(c, n.sons[L-1])
|
||||
closeScope(c)
|
||||
of nkBlockStmt, nkBlockExpr, nkBlockType:
|
||||
checkSonsLen(n, 2)
|
||||
openScope(c)
|
||||
if n.sons[0].kind != nkEmpty:
|
||||
# labels are always 'gensym'ed:
|
||||
let s = newGenSym(skLabel, n.sons[0], c)
|
||||
addPrelimDecl(c.c, s)
|
||||
n.sons[0] = newSymNode(s, n.sons[0].info)
|
||||
n.sons[1] = semTemplBody(c, n.sons[1])
|
||||
closeScope(c)
|
||||
of nkTryStmt:
|
||||
checkMinSonsLen(n, 2)
|
||||
n.sons[0] = semTemplBodyScope(c, n.sons[0])
|
||||
for i in countup(1, sonsLen(n)-1):
|
||||
var a = n.sons[i]
|
||||
checkMinSonsLen(a, 1)
|
||||
var L = sonsLen(a)
|
||||
for j in countup(0, L-2):
|
||||
a.sons[j] = semTemplBody(c, a.sons[j])
|
||||
a.sons[L-1] = semTemplBodyScope(c, a.sons[L-1])
|
||||
of nkVarSection, nkLetSection:
|
||||
let symKind = if n.kind == nkLetSection: skLet else: skVar
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
var a = n.sons[i]
|
||||
if a.kind == nkCommentStmt: continue
|
||||
if (a.kind != nkIdentDefs) and (a.kind != nkVarTuple): IllFormedAst(a)
|
||||
checkMinSonsLen(a, 3)
|
||||
var L = sonsLen(a)
|
||||
a.sons[L-2] = semTemplBody(c, a.sons[L-2])
|
||||
a.sons[L-1] = semTemplBody(c, a.sons[L-1])
|
||||
for j in countup(0, L-3):
|
||||
addLocalDecl(c, a.sons[j], symKind)
|
||||
of nkConstSection:
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
var a = n.sons[i]
|
||||
if a.kind == nkCommentStmt: continue
|
||||
if (a.kind != nkConstDef): IllFormedAst(a)
|
||||
checkSonsLen(a, 3)
|
||||
addLocalDecl(c, a.sons[0], skConst)
|
||||
a.sons[1] = semTemplBody(c, a.sons[1])
|
||||
a.sons[2] = semTemplBody(c, a.sons[2])
|
||||
of nkTypeSection:
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
var a = n.sons[i]
|
||||
if a.kind == nkCommentStmt: continue
|
||||
if (a.kind != nkTypeDef): IllFormedAst(a)
|
||||
checkSonsLen(a, 3)
|
||||
addLocalDecl(c, a.sons[0], skType)
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
var a = n.sons[i]
|
||||
if a.kind == nkCommentStmt: continue
|
||||
if (a.kind != nkTypeDef): IllFormedAst(a)
|
||||
checkSonsLen(a, 3)
|
||||
if a.sons[1].kind != nkEmpty:
|
||||
openScope(c)
|
||||
a.sons[1] = semTemplBody(c, a.sons[1])
|
||||
a.sons[2] = semTemplBody(c, a.sons[2])
|
||||
closeScope(c)
|
||||
else:
|
||||
a.sons[2] = semTemplBody(c, a.sons[2])
|
||||
of nkProcDef, nkLambdaKinds:
|
||||
result = semRoutineInTemplBody(c, n, skProc)
|
||||
of nkMethodDef:
|
||||
result = semRoutineInTemplBody(c, n, skMethod)
|
||||
of nkIteratorDef:
|
||||
result = semRoutineInTemplBody(c, n, skIterator)
|
||||
of nkTemplateDef:
|
||||
result = semRoutineInTemplBody(c, n, skTemplate)
|
||||
of nkMacroDef:
|
||||
result = semRoutineInTemplBody(c, n, skMacro)
|
||||
of nkConverterDef:
|
||||
result = semRoutineInTemplBody(c, n, skConverter)
|
||||
else:
|
||||
# dotExpr is ambiguous: note that we explicitely allow 'x.TemplateParam',
|
||||
# so we use the generic code for nkDotExpr too
|
||||
if n.kind == nkDotExpr or n.kind == nkAccQuoted:
|
||||
let s = QualifiedLookUp(c, n, {})
|
||||
if s != nil and Contains(toBind, s.id):
|
||||
return symChoice(c, n, s)
|
||||
let s = QualifiedLookUp(c.c, n, {})
|
||||
if s != nil and Contains(c.toBind, s.id):
|
||||
return symChoice(c.c, n, s)
|
||||
result = n
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
result.sons[i] = resolveTemplateParams(c, n.sons[i], owner, toBind)
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
result.sons[i] = semTemplBody(c, n.sons[i])
|
||||
|
||||
proc transformToExpr(n: PNode): PNode =
|
||||
var realStmt: int
|
||||
|
|
@ -94,8 +304,8 @@ proc transformToExpr(n: PNode): PNode =
|
|||
else: n.kind = nkStmtListExpr
|
||||
of nkBlockStmt:
|
||||
n.kind = nkBlockExpr
|
||||
#nkIfStmt: n.kind := nkIfExpr; // this is not correct!
|
||||
else:
|
||||
#nkIfStmt: n.kind = nkIfExpr // this is not correct!
|
||||
else:
|
||||
nil
|
||||
|
||||
proc semTemplateDef(c: PContext, n: PNode): PNode =
|
||||
|
|
@ -108,7 +318,7 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
|
|||
# check parameter list:
|
||||
pushOwner(s)
|
||||
openScope(c.tab)
|
||||
n.sons[namePos] = newSymNode(s)
|
||||
n.sons[namePos] = newSymNode(s, n.sons[namePos].info)
|
||||
if n.sons[pragmasPos].kind != nkEmpty:
|
||||
pragma(c, s, n.sons[pragmasPos], templatePragmas)
|
||||
# check that no generic parameters exist:
|
||||
|
|
@ -126,8 +336,11 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
|
|||
# use ``stmt`` as implicit result type
|
||||
s.typ.sons[0] = newTypeS(tyStmt, c)
|
||||
s.typ.n.sons[0] = newNodeIT(nkType, n.info, s.typ.sons[0])
|
||||
var toBind = initIntSet()
|
||||
n.sons[bodyPos] = resolveTemplateParams(c, n.sons[bodyPos], s, toBind)
|
||||
var ctx: TemplCtx
|
||||
ctx.toBind = initIntSet()
|
||||
ctx.c = c
|
||||
ctx.owner = s
|
||||
n.sons[bodyPos] = semTemplBody(ctx, n.sons[bodyPos])
|
||||
if s.typ.sons[0].kind notin {tyStmt, tyTypeDesc}:
|
||||
n.sons[bodyPos] = transformToExpr(n.sons[bodyPos])
|
||||
# only parameters are resolved, no type checking is performed
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue