first steps to make templates hygienic

This commit is contained in:
Araq
2012-08-20 01:13:13 +02:00
parent 0cac8d9b6f
commit 5e15dec175
23 changed files with 486 additions and 187 deletions

View File

@@ -234,7 +234,7 @@ type
sfDiscriminant, # field is a discriminant in a record/object
sfDeprecated, # symbol is deprecated
sfError, # usage of symbol should trigger a compile-time error
sfInnerProc, # proc is an inner proc
sfShadowed, # a symbol that was shadowed in some inner scope
sfThread, # proc will run as a thread
# variable is a thread variable
sfCompileTime, # proc can be evaluated at compile time
@@ -247,7 +247,7 @@ type
# language; for interfacing with Objective C
sfDiscardable # returned value may be discarded implicitely
sfDestructor # proc is destructor
sfByCopy # a variable is to be captured by value in a closure
sfGenSym # symbol is 'gensym'ed; do not add to symbol table
TSymFlags* = set[TSymFlag]
@@ -264,10 +264,7 @@ const
# symbol name that was generated by the compiler
# the compiler will avoid printing such names
# in user messages.
sfShadowed* = sfInnerProc
# a variable that was shadowed in some inner scope
sfHoist* = sfVolatile ## proc return value can be hoisted
const
@@ -785,15 +782,6 @@ const # for all kind of hash tables:
GrowthFactor* = 2 # must be power of 2, > 0
StartSize* = 8 # must be power of 2, > 0
proc ValueToString*(a: PNode): string =
case a.kind
of nkCharLit..nkUInt64Lit: result = $(a.intVal)
of nkFloatLit..nkFloat128Lit: result = $(a.floatVal)
of nkStrLit..nkTripleStrLit: result = a.strVal
else:
InternalError(a.info, "valueToString")
result = ""
proc copyStrTable(dest: var TStrTable, src: TStrTable) =
dest.counter = src.counter
if isNil(src.data): return

View File

@@ -166,7 +166,9 @@ proc SameValue*(a, b: PNode): bool =
of nkStrLit..nkTripleStrLit:
if b.kind in {nkStrLit..nkTripleStrLit}: result = a.strVal == b.strVal
else:
InternalError(a.info, "SameValue")
# don't raise an internal error for 'nimrod check':
#InternalError(a.info, "SameValue")
nil
proc leValue*(a, b: PNode): bool =
# a <= b?
@@ -178,7 +180,10 @@ proc leValue*(a, b: PNode): bool =
if b.kind in {nkFloatLit..nkFloat64Lit}: result = a.floatVal <= b.floatVal
of nkStrLit..nkTripleStrLit:
if b.kind in {nkStrLit..nkTripleStrLit}: result = a.strVal <= b.strVal
else: InternalError(a.info, "leValue")
else:
# don't raise an internal error for 'nimrod check':
#InternalError(a.info, "leValue")
nil
proc lookupInRecord(n: PNode, field: PIdent): PSym =
result = nil

View File

@@ -224,13 +224,16 @@ proc processMergeInfo(L: var TBaseLexer, m: BModule) =
of "labels": m.labels = decodeVInt(L.buf, L.bufpos)
of "hasframe": m.FrameDeclared = decodeVInt(L.buf, L.bufpos) != 0
else: InternalError("ccgmerge: unkown key: " & k)
when not defined(nimhygiene):
{.pragma: inject.}
template withCFile(cfilename: string, body: stmt) =
var s = LLStreamOpen(cfilename, fmRead)
if s == nil: return
var L: TBaseLexer
var L {.inject.}: TBaseLexer
openBaseLexer(L, s)
var k = newStringOfCap("NIM_merge_FORWARD_TYPES".len)
var k {.inject.} = newStringOfCap("NIM_merge_FORWARD_TYPES".len)
while true:
skipUntilCmd(L)
if ^L.bufpos == '\0': break

View File

@@ -55,6 +55,8 @@ proc countDefinedSymbols*(): int =
proc InitDefines*() =
initStrTable(gSymbols)
DefineSymbol("nimrod") # 'nimrod' is always defined
# for bootstrapping purposes and old code:
DefineSymbol("nimhygiene")
# add platform specific symbols:
case targetCPU

View File

@@ -16,7 +16,7 @@
import
strutils, magicsys, lists, options, ast, astalgo, trees, treetab, nimsets,
msgs, os, condsyms, idents, renderer, types, passes, semfold, transf,
parser, ropes, rodread, idgen, osproc, streams
parser, ropes, rodread, idgen, osproc, streams, evaltempl
type
PStackFrame* = ref TStackFrame
@@ -850,56 +850,6 @@ proc evalParseStmt(c: PEvalContext, n: PNode): PNode =
result = parseString(code.getStrValue, code.info.toFilename,
code.stringStartingLine)
result.typ = newType(tyStmt, c.module)
proc evalTemplateAux*(templ, actual: PNode, sym: PSym): PNode =
inc genSymBaseId
case templ.kind
of nkSym:
var p = templ.sym
if (p.kind == skParam) and (p.owner.id == sym.id):
result = copyTree(actual.sons[p.position])
else:
result = copyNode(templ)
of nkNone..nkIdent, nkType..nkNilLit: # atom
result = copyNode(templ)
else:
result = copyNode(templ)
newSons(result, sonsLen(templ))
for i in countup(0, sonsLen(templ) - 1):
result.sons[i] = evalTemplateAux(templ.sons[i], actual, sym)
proc evalTemplateArgs(n: PNode, s: PSym): PNode =
# if the template has zero arguments, it can be called without ``()``
# `n` is then a nkSym or something similar
var a: int
case n.kind
of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit:
a = sonsLen(n)
else: a = 0
var f = s.typ.sonsLen
if a > f: GlobalError(n.info, errWrongNumberOfArguments)
result = copyNode(n)
for i in countup(1, f - 1):
var arg = if i < a: n.sons[i] else: copyTree(s.typ.n.sons[i].sym.ast)
if arg == nil or arg.kind == nkEmpty:
LocalError(n.info, errWrongNumberOfArguments)
addSon(result, arg)
var evalTemplateCounter* = 0
# to prevent endless recursion in templates instantation
proc evalTemplate*(n: PNode, sym: PSym): PNode =
inc(evalTemplateCounter)
if evalTemplateCounter > 100:
GlobalError(n.info, errTemplateInstantiationTooNested)
result = n
# replace each param by the corresponding node:
var args = evalTemplateArgs(n, sym)
result = evalTemplateAux(sym.getBody, args, sym)
dec(evalTemplateCounter)
proc evalTypeTrait*(n: PNode, context: PSym): PNode =
## XXX: This should be pretty much guaranteed to be true
@@ -964,7 +914,11 @@ proc evalExpandToAst(c: PEvalContext, original: PNode): PNode =
case expandedSym.kind
of skTemplate:
result = evalTemplate(macroCall, expandedSym)
let genSymOwner = if c.tos != nil and c.tos.prc != nil:
c.tos.prc
else:
c.module
result = evalTemplate(macroCall, expandedSym, genSymOwner)
of skMacro:
# At this point macroCall.sons[0] is nkSym node.
# To be completely compatible with normal macro invocation,

83
compiler/evaltempl.nim Normal file
View File

@@ -0,0 +1,83 @@
#
#
# The Nimrod Compiler
# (c) Copyright 2012 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,
rodread
type
TemplCtx {.pure, final.} = object
owner, genSymOwner: PSym
mapping: TIdTable # every gensym'ed symbol needs to be mapped to some
# new symbol
proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx): PNode =
#inc genSymBaseId
case templ.kind
of nkSym:
var s = templ.sym
if s.owner.id == c.owner.id:
if s.kind == skParam:
result = copyTree(actual.sons[s.position])
else:
InternalAssert sfGenSym in s.flags
var x = PSym(IdTableGet(c.mapping, s))
if x == nil:
x = copySym(s, false)
x.owner = c.genSymOwner
IdTablePut(c.mapping, s, x)
result = newSymNode(x, templ.info)
else:
result = copyNode(templ)
of nkNone..nkIdent, nkType..nkNilLit: # atom
result = copyNode(templ)
else:
result = copyNode(templ)
newSons(result, sonsLen(templ))
for i in countup(0, sonsLen(templ) - 1):
result.sons[i] = evalTemplateAux(templ.sons[i], actual, c)
proc evalTemplateArgs(n: PNode, s: PSym): PNode =
# if the template has zero arguments, it can be called without ``()``
# `n` is then a nkSym or something similar
var a: int
case n.kind
of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit:
a = sonsLen(n)
else: a = 0
var f = s.typ.sonsLen
if a > f: GlobalError(n.info, errWrongNumberOfArguments)
result = copyNode(n)
for i in countup(1, f - 1):
var arg = if i < a: n.sons[i] else: copyTree(s.typ.n.sons[i].sym.ast)
if arg == nil or arg.kind == nkEmpty:
LocalError(n.info, errWrongNumberOfArguments)
addSon(result, arg)
var evalTemplateCounter* = 0
# to prevent endless recursion in templates instantiation
proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym): PNode =
inc(evalTemplateCounter)
if evalTemplateCounter > 100:
GlobalError(n.info, errTemplateInstantiationTooNested)
result = n
# replace each param by the corresponding node:
var args = evalTemplateArgs(n, tmpl)
var ctx: TemplCtx
ctx.owner = tmpl
ctx.genSymOwner = genSymOwner
initIdTable(ctx.mapping)
result = evalTemplateAux(tmpl.getBody, args, ctx)
dec(evalTemplateCounter)

View File

@@ -471,7 +471,8 @@ proc generateClosureCreation(o: POuterContext, scope: PEnv): PNode =
# add assignment statements:
for local in scope.capturedVars:
let fieldAccess = indirectAccess(env, local, env.info)
if sfByCopy in local.flags or local.kind == skParam:
if local.kind == skParam:
# maybe later: (sfByCopy in local.flags)
# add ``env.param = param``
result.add(newAsgnStmt(fieldAccess, newSymNode(local)))
IdNodeTablePut(o.localsToAccess, local, fieldAccess)
@@ -515,9 +516,9 @@ proc transformOuterProc(o: POuterContext, n: PNode): PNode =
scope.sons[0] = generateClosureCreation(o, env)
# change 'local' to 'closure.local', unless it's a 'byCopy' variable:
if sfByCopy notin local.flags:
result = IdNodeTableGet(o.localsToAccess, local)
assert result != nil, "cannot find: " & local.name.s
# if sfByCopy notin local.flags:
result = IdNodeTableGet(o.localsToAccess, local)
assert result != nil, "cannot find: " & local.name.s
# else it is captured by copy and this means that 'outer' should continue
# to access the local as a local.
of nkLambdaKinds:

View File

@@ -111,7 +111,7 @@ proc jumpToDirective(L: var TLexer, tok: var TToken, dest: TJumpDest) =
proc parseDirective(L: var TLexer, tok: var TToken) =
ppGetTok(L, tok) # skip @
case whichKeyword(tok.ident)
of wIf:
of wIf:
setlen(condStack, len(condStack) + 1)
var res = EvalppIf(L, tok)
condStack[high(condStack)] = res
@@ -123,25 +123,27 @@ proc parseDirective(L: var TLexer, tok: var TToken) =
ppGetTok(L, tok)
msgs.MsgWriteln(tokToStr(tok))
ppGetTok(L, tok)
of wPutEnv:
ppGetTok(L, tok)
var key = tokToStr(tok)
ppGetTok(L, tok)
os.putEnv(key, tokToStr(tok))
ppGetTok(L, tok)
of wPrependEnv:
ppGetTok(L, tok)
var key = tokToStr(tok)
ppGetTok(L, tok)
os.putEnv(key, tokToStr(tok) & os.getenv(key))
ppGetTok(L, tok)
of wAppendenv:
ppGetTok(L, tok)
var key = tokToStr(tok)
ppGetTok(L, tok)
os.putEnv(key, os.getenv(key) & tokToStr(tok))
ppGetTok(L, tok)
else: lexMessage(L, errInvalidDirectiveX, tokToStr(tok))
else:
case tok.ident.s.normalize
of "putenv":
ppGetTok(L, tok)
var key = tokToStr(tok)
ppGetTok(L, tok)
os.putEnv(key, tokToStr(tok))
ppGetTok(L, tok)
of "prependenv":
ppGetTok(L, tok)
var key = tokToStr(tok)
ppGetTok(L, tok)
os.putEnv(key, tokToStr(tok) & os.getenv(key))
ppGetTok(L, tok)
of "appendenv":
ppGetTok(L, tok)
var key = tokToStr(tok)
ppGetTok(L, tok)
os.putEnv(key, os.getenv(key) & tokToStr(tok))
ppGetTok(L, tok)
else: lexMessage(L, errInvalidDirectiveX, tokToStr(tok))
proc confTok(L: var TLexer, tok: var TToken) =
ppGetTok(L, tok)

View File

@@ -23,16 +23,17 @@ const
wMagic, wNosideEffect, wSideEffect, wNoreturn, wDynLib, wHeader,
wCompilerProc, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge,
wBorrow, wExtern, wImportCompilerProc, wThread, wImportCpp, wImportObjC,
wNoStackFrame, wError, wDiscardable, wNoInit, wDestructor, wHoist}
wNoStackFrame, wError, wDiscardable, wNoInit, wDestructor, wHoist,
wGenSym, wInject}
converterPragmas* = procPragmas
methodPragmas* = procPragmas
templatePragmas* = {wImmediate, wDeprecated, wError}
templatePragmas* = {wImmediate, wDeprecated, wError, wGenSym, wInject}
macroPragmas* = {FirstCallConv..LastCallConv, wImmediate, wImportc, wExportc,
wNodecl, wMagic, wNosideEffect, wCompilerProc, wDeprecated, wExtern,
wImportcpp, wImportobjc, wError, wDiscardable}
wImportcpp, wImportobjc, wError, wDiscardable, wGenSym, wInject}
iteratorPragmas* = {FirstCallConv..LastCallConv, wNosideEffect, wSideEffect,
wImportc, wExportc, wNodecl, wMagic, wDeprecated, wBorrow, wExtern,
wImportcpp, wImportobjc, wError, wDiscardable}
wImportcpp, wImportobjc, wError, wDiscardable, wGenSym, wInject}
exprPragmas* = {wLine}
stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangechecks,
wBoundchecks, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints,
@@ -46,14 +47,16 @@ const
wDeprecated, wExtern, wThread, wImportcpp, wImportobjc, wNoStackFrame}
typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl,
wPure, wHeader, wCompilerProc, wFinal, wSize, wExtern, wShallow,
wImportcpp, wImportobjc, wError, wIncompleteStruct, wByCopy, wByRef}
wImportcpp, wImportobjc, wError, wIncompleteStruct, wByCopy, wByRef,
wGenSym, wInject}
fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern,
wImportcpp, wImportobjc, wError}
varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl,
wMagic, wHeader, wDeprecated, wCompilerProc, wDynLib, wExtern,
wImportcpp, wImportobjc, wError, wNoInit, wCompileTime, wGlobal}
wImportcpp, wImportobjc, wError, wNoInit, wCompileTime, wGlobal,
wGenSym, wInject}
constPragmas* = {wImportc, wExportc, wHeader, wDeprecated, wMagic, wNodecl,
wExtern, wImportcpp, wImportobjc, wError}
wExtern, wImportcpp, wImportobjc, wError, wGenSym, wInject}
letPragmas* = varPragmas
procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNosideEffect,
wThread}
@@ -655,9 +658,13 @@ proc pragma(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) =
incl(sym.typ.flags, tfByRef)
of wByCopy:
noVal(it)
if sym.kind != skType: incl(sym.flags, sfByCopy)
elif sym.typ == nil: invalidPragma(it)
if sym.kind != skType or sym.typ == nil: invalidPragma(it)
else: incl(sym.typ.flags, tfByCopy)
of wInject, wGenSym:
# We check for errors, but do nothing with these pragmas otherwise
# as they are handled directly in 'evalTemplate'.
noVal(it)
if sym == nil: invalidPragma(it)
of wLine: PragmaLine(c, it)
else: invalidPragma(it)
else: invalidPragma(it)

View File

@@ -14,7 +14,8 @@ import
wordrecg, ropes, msgs, os, condsyms, idents, renderer, types, platform, math,
magicsys, parser, nversion, nimsets, semfold, importer,
procfind, lookups, rodread, pragmas, passes, semdata, semtypinst, sigmatch,
semthreads, intsets, transf, evals, idgen, aliases, cgmeth, lambdalifting
semthreads, intsets, transf, evals, idgen, aliases, cgmeth, lambdalifting,
evaltempl
proc semPass*(): TPass
# implementation
@@ -56,7 +57,17 @@ proc isTopLevel(c: PContext): bool {.inline.} =
proc newSymS(kind: TSymKind, n: PNode, c: PContext): PSym =
result = newSym(kind, considerAcc(n), getCurrOwner())
result.info = n.info
proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym =
# like newSymS, but considers gensym'ed symbols
if n.kind == nkSym:
result = n.sym
InternalAssert sfGenSym in result.flags
InternalAssert result.kind == kind
else:
result = newSym(kind, considerAcc(n), getCurrOwner())
result.info = n.info
proc semIdentVis(c: PContext, kind: TSymKind, n: PNode,
allowed: TSymFlags): PSym
# identifier with visability

View File

@@ -24,7 +24,7 @@ proc restoreOldStyleType(n: PNode) =
proc semTemplateExpr(c: PContext, n: PNode, s: PSym, semCheck = true): PNode =
markUsed(n, s)
pushInfoContext(n.info)
result = evalTemplate(n, s)
result = evalTemplate(n, s, getCurrOwner())
if semCheck: result = semAfterMacroCall(c, result, s)
popInfoContext()
@@ -1372,7 +1372,9 @@ proc semBlockExpr(c: PContext, n: PNode): PNode =
Inc(c.p.nestedBlockCounter)
checkSonsLen(n, 2)
openScope(c.tab) # BUGFIX: label is in the scope of block!
if n.sons[0].kind != nkEmpty: addDecl(c, newSymS(skLabel, n.sons[0], c))
if n.sons[0].kind notin {nkEmpty, nkSym}:
# nkSym for gensym'ed labels:
addDecl(c, newSymS(skLabel, n.sons[0], c))
n.sons[1] = semStmtListExpr(c, n.sons[1])
n.typ = n.sons[1].typ
closeScope(c.tab)

View File

@@ -17,11 +17,20 @@
# included from sem.nim
type
TSemGenericFlag = enum
type
TSemGenericFlag = enum
withinBind, withinTypeDesc
TSemGenericFlags = set[TSemGenericFlag]
proc getIdentNode(n: PNode): PNode =
case n.kind
of nkPostfix: result = getIdentNode(n.sons[1])
of nkPragmaExpr: result = getIdentNode(n.sons[0])
of nkIdent, nkAccQuoted, nkSym: result = n
else:
illFormedAst(n)
result = n
proc semGenericStmt(c: PContext, n: PNode, flags: TSemGenericFlags,
toBind: var TIntSet): PNode
proc semGenericStmtScope(c: PContext, n: PNode,
@@ -54,15 +63,6 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym): PNode =
result = n
else: result = newSymNode(s, n.info)
proc getIdentNode(n: PNode): PNode =
case n.kind
of nkPostfix: result = getIdentNode(n.sons[1])
of nkPragmaExpr: result = getIdentNode(n.sons[0])
of nkIdent, nkAccQuoted: result = n
else:
illFormedAst(n)
result = n
proc semGenericStmt(c: PContext, n: PNode,
flags: TSemGenericFlags, toBind: var TIntSet): PNode =
result = n

View File

@@ -91,10 +91,11 @@ proc semBlock(c: PContext, n: PNode): PNode =
Inc(c.p.nestedBlockCounter)
checkSonsLen(n, 2)
openScope(c.tab) # BUGFIX: label is in the scope of block!
if n.sons[0].kind != nkEmpty:
var labl = newSymS(skLabel, n.sons[0], c)
addDecl(c, labl)
n.sons[0] = newSymNode(labl)
if n.sons[0].kind != nkEmpty:
var labl = newSymG(skLabel, n.sons[0], c)
if sfGenSym notin labl.flags:
addDecl(c, labl)
n.sons[0] = newSymNode(labl, n.sons[0].info)
suggestSym(n.sons[0], labl)
n.sons[1] = semStmt(c, n.sons[1])
closeScope(c.tab)
@@ -284,7 +285,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
Message(a.info, warnEachIdentIsTuple)
for j in countup(0, length-3):
var v = semIdentDef(c, a.sons[j], symkind)
addInterfaceDecl(c, v)
if sfGenSym notin v.flags: addInterfaceDecl(c, v)
when oKeepVariableNames:
if c.InUnrolledContext > 0: v.flags.incl(sfShadowed)
else:
@@ -332,7 +333,7 @@ proc semConst(c: PContext, n: PNode): PNode =
continue
v.typ = typ
v.ast = def # no need to copy
addInterfaceDecl(c, v)
if sfGenSym notin v.flags: addInterfaceDecl(c, v)
var b = newNodeI(nkConstDef, a.info)
addSon(b, newSymNode(v))
addSon(b, ast.emptyNode) # no type description
@@ -422,25 +423,25 @@ proc semForVars(c: PContext, n: PNode): PNode =
# and thus no tuple unpacking:
if iter.kind != tyTuple or length == 3:
if length == 3:
var v = newSymS(skForVar, n.sons[0], c)
var v = newSymG(skForVar, n.sons[0], c)
if getCurrOwner().kind == skModule: incl(v.flags, sfGlobal)
# BUGFIX: don't use `iter` here as that would strip away
# the ``tyGenericInst``! See ``tests/compile/tgeneric.nim``
# for an example:
v.typ = n.sons[length-2].typ
n.sons[0] = newSymNode(v)
addDecl(c, v)
if sfGenSym notin v.flags: addDecl(c, v)
else:
LocalError(n.info, errWrongNumberOfVariables)
elif length-2 != sonsLen(iter):
LocalError(n.info, errWrongNumberOfVariables)
else:
for i in countup(0, length - 3):
var v = newSymS(skForVar, n.sons[i], c)
var v = newSymG(skForVar, n.sons[i], c)
if getCurrOwner().kind == skModule: incl(v.flags, sfGlobal)
v.typ = iter.sons[i]
n.sons[i] = newSymNode(v)
addDecl(c, v)
if sfGenSym notin v.flags: addDecl(c, v)
Inc(c.p.nestedLoopCounter)
n.sons[length-1] = SemStmt(c, n.sons[length-1])
Dec(c.p.nestedLoopCounter)
@@ -539,7 +540,7 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) =
if a.sons[0].kind == nkPragmaExpr:
pragma(c, s, a.sons[0].sons[1], typePragmas)
# add it here, so that recursive types are possible:
addInterfaceDecl(c, s)
if sfGenSym notin s.flags: addInterfaceDecl(c, s)
a.sons[0] = newSymNode(s)
proc typeSectionRightSidePass(c: PContext, n: PNode) =
@@ -780,7 +781,8 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
s.typ.callConv = lastOptionEntry(c).defaultCC
# add it here, so that recursive procs are possible:
# -2 because we have a scope open for parameters
if kind in OverloadableSyms:
if sfGenSym in s.flags: nil
elif kind in OverloadableSyms:
addInterfaceOverloadableSymAt(c, s, c.tab.tos - 2)
else:
addInterfaceDeclAt(c, s, c.tab.tos - 2)

View File

@@ -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

View File

@@ -78,7 +78,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
incl(e.flags, sfExported) # BUGFIX
StrTableAdd(c.module.tab, e) # BUGFIX
addSon(result.n, newSymNode(e))
addDeclAt(c, e, c.tab.tos - 1)
if sfGenSym notin e.flags: addDeclAt(c, e, c.tab.tos - 1)
inc(counter)
proc semSet(c: PContext, n: PNode, prev: PType): PType =
@@ -264,7 +264,7 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
if a.sons[length - 1].kind != nkEmpty:
LocalError(a.sons[length - 1].info, errInitHereNotAllowed)
for j in countup(0, length - 3):
var field = newSymS(skField, a.sons[j], c)
var field = newSymG(skField, a.sons[j], c)
field.typ = typ
field.position = counter
inc(counter)
@@ -279,7 +279,9 @@ proc semIdentVis(c: PContext, kind: TSymKind, n: PNode,
# identifier with visibility
if n.kind == nkPostfix:
if sonsLen(n) == 2 and n.sons[0].kind == nkIdent:
result = newSymS(kind, n.sons[1], c)
# for gensym'ed identifiers the identifier may already have been
# transformed to a symbol and we need to use that here:
result = newSymG(kind, n.sons[1], c)
var v = n.sons[0].ident
if sfExported in allowed and v.id == ord(wStar):
incl(result.flags, sfExported)
@@ -288,7 +290,7 @@ proc semIdentVis(c: PContext, kind: TSymKind, n: PNode,
else:
illFormedAst(n)
else:
result = newSymS(kind, n, c)
result = newSymG(kind, n, c)
proc semIdentWithPragma(c: PContext, kind: TSymKind, n: PNode,
allowed: TSymFlags): PSym =
@@ -469,6 +471,7 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var TIntSet, pos: var int,
typ = semTypeNode(c, n.sons[length-2], nil)
for i in countup(0, sonsLen(n)-3):
var f = semIdentWithPragma(c, skField, n.sons[i], {sfExported})
suggestSym(n.sons[i], f)
f.typ = typ
f.position = pos
if (rectype != nil) and ({sfImportc, sfExportc} * rectype.flags != {}) and
@@ -548,9 +551,9 @@ proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind) =
let nn = getSysSym"PNimrodNode"
var a = copySym(param)
a.typ = nn.typ
addDecl(c, a)
if sfGenSym notin a.flags: addDecl(c, a)
else:
addDecl(c, param)
if sfGenSym notin param.flags: addDecl(c, param)
proc paramTypeClass(c: PContext, paramType: PType, procKind: TSymKind):
tuple[typ: PType, id: PIdent] =
@@ -672,7 +675,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
if skipTypes(typ, {tyGenericInst}).kind == tyEmpty: continue
for j in countup(0, length-3):
var arg = newSymS(skParam, a.sons[j], c)
var arg = newSymG(skParam, a.sons[j], c)
var finalType = liftParamType(c, kind, genericParams, typ, arg.name.s,
arg.info).skipIntLit
arg.typ = finalType
@@ -711,7 +714,7 @@ proc semBlockType(c: PContext, n: PNode, prev: PType): PType =
Inc(c.p.nestedBlockCounter)
checkSonsLen(n, 2)
openScope(c.tab)
if n.sons[0].kind != nkEmpty:
if n.sons[0].kind notin {nkEmpty, nkSym}:
addDecl(c, newSymS(skLabel, n.sons[0], c))
result = semStmtListType(c, n.sons[1], prev)
n.sons[1].typ = result
@@ -947,7 +950,7 @@ proc semGenericConstraints(c: PContext, n: PNode, result: PType) =
proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode =
result = copyNode(n)
if n.kind != nkGenericParams:
InternalError(n.info, "semGenericParamList")
illFormedAst(n)
return
for i in countup(0, sonsLen(n)-1):
var a = n.sons[i]
@@ -965,27 +968,27 @@ proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode =
for j in countup(0, L-3):
var s: PSym
if typ == nil:
s = newSymS(skType, a.sons[j], c)
s = newSymG(skType, a.sons[j], c)
s.typ = newTypeS(tyGenericParam, c)
else:
case typ.kind
of tyTypeDesc:
s = newSymS(skType, a.sons[j], c)
s = newSymG(skType, a.sons[j], c)
s.typ = newTypeS(tyGenericParam, c)
of tyExpr:
#echo "GENERIC EXPR ", a.info.toFileLineCol
# not a type param, but an expression
# proc foo[x: expr](bar: int) what is this?
s = newSymS(skGenericParam, a.sons[j], c)
s = newSymG(skGenericParam, a.sons[j], c)
s.typ = typ
else:
# This handles cases like proc foo[t: tuple]
# XXX: we want to turn that into a type class
s = newSymS(skType, a.sons[j], c)
s = newSymG(skType, a.sons[j], c)
s.typ = typ
if def.kind != nkEmpty: s.ast = def
s.typ.sym = s
if father != nil: addSonSkipIntLit(father, s.typ)
s.position = i
addSon(result, newSymNode(s))
addDecl(c, s)
if sfGenSym notin s.flags: addDecl(c, s)

View File

@@ -54,9 +54,13 @@ proc suggestField(c: PContext, s: PSym, outputs: var int) =
OutWriteln(SymToStr(s, isLocal=true, sectionSuggest))
inc outputs
template wholeSymTab(cond, section: expr) {.immediate.} =
for i in countdown(c.tab.tos-1, 0):
for it in items(c.tab.stack[i]):
when not defined(nimhygiene):
{.pragma: inject.}
template wholeSymTab(cond, section: expr) {.immediate.} =
for i in countdown(c.tab.tos-1, 0):
for item in items(c.tab.stack[i]):
let it {.inject.} = item
if cond:
OutWriteln(SymToStr(it, isLocal = i > ModuleTablePos, section))
inc outputs

View File

@@ -380,6 +380,13 @@ proc mutateType(t: PType, iter: TTypeMutator, closure: PObject): PType =
var marker = InitIntSet()
result = mutateTypeAux(marker, t, iter, closure)
proc ValueToString(a: PNode): string =
case a.kind
of nkCharLit..nkUInt64Lit: result = $(a.intVal)
of nkFloatLit..nkFloat128Lit: result = $(a.floatVal)
of nkStrLit..nkTripleStrLit: result = a.strVal
else: result = "<invalid value>"
proc rangeToStr(n: PNode): string =
assert(n.kind == nkRange)
result = ValueToString(n.sons[0]) & ".." & ValueToString(n.sons[1])

View File

@@ -60,7 +60,7 @@ type
wFieldChecks,
wWatchPoint, wSubsChar,
wAcyclic, wShallow, wUnroll, wLinearScanEnd,
wWrite, wPutEnv, wPrependEnv, wAppendEnv, wThreadVar, wEmit, wNoStackFrame,
wWrite, wGensym, wInject, wInheritable, wThreadVar, wEmit, wNoStackFrame,
wImplicitStatic, wGlobal, wHoist
wAuto, wBool, wCatch, wChar, wClass,
@@ -138,7 +138,7 @@ const
"passc", "passl", "borrow", "discardable", "fieldchecks",
"watchpoint",
"subschar", "acyclic", "shallow", "unroll", "linearscanend",
"write", "putenv", "prependenv", "appendenv", "threadvar", "emit",
"write", "gensym", "inject", "inheritable", "threadvar", "emit",
"nostackframe", "implicitstatic", "global", "hoist",
"auto", "bool", "catch", "char", "class",

View File

@@ -908,7 +908,7 @@ converts the list of arguments to an array implicitely:
write(f, "\n")
myWriteln(stdout, "abc", "def", "xyz")
# is transformed by the compiler to:
# is transformed to:
myWriteln(stdout, ["abc", "def", "xyz"])
This transformation is only done if the varargs parameter is the
@@ -922,7 +922,7 @@ type conversions in this context:
write(f, "\n")
myWriteln(stdout, 123, "abc", 4.0)
# is transformed by the compiler to:
# is transformed to:
myWriteln(stdout, [$123, $"def", $4.0])
In this example ``$`` is applied to any argument that is passed to the

View File

@@ -218,6 +218,9 @@ proc findAll*(s: string, pattern: TRegEx, start = 0): seq[string] =
## If it does not match, @[] is returned.
accumulateResult(findAll(s, pattern, start))
when not defined(nimhygiene):
{.pragma: inject.}
template `=~` *(s: string, pattern: TRegEx): expr =
## This calls ``match`` with an implicit declared ``matches`` array that
## can be used in the scope of the ``=~`` call:
@@ -237,7 +240,7 @@ template `=~` *(s: string, pattern: TRegEx): expr =
## echo("syntax error")
##
when not definedInScope(matches):
var matches: array[0..re.maxSubPatterns-1, string]
var matches {.inject.}: array[0..re.maxSubPatterns-1, string]
match(s, pattern, matches)
# ------------------------- more string handling ------------------------------

View File

@@ -845,6 +845,9 @@ proc findAll*(s: string, pattern: TPeg, start = 0): seq[string] {.
## returns all matching *substrings* of `s` that match `pattern`.
## If it does not match, @[] is returned.
accumulateResult(findAll(s, pattern, start))
when not defined(nimhygiene):
{.pragma: inject.}
template `=~`*(s: string, pattern: TPeg): bool =
## This calls ``match`` with an implicit declared ``matches`` array that
@@ -865,7 +868,7 @@ template `=~`*(s: string, pattern: TPeg): bool =
## echo("syntax error")
##
when not definedInScope(matches):
var matches: array[0..pegs.maxSubpatterns-1, string]
var matches {.inject.}: array[0..pegs.maxSubpatterns-1, string]
match(s, pattern, matches)
# ------------------------- more string handling ------------------------------

View File

@@ -62,12 +62,15 @@ proc extractSpec(filename: string): string =
#echo "warning: file does not contain spec: " & filename
result = ""
when not defined(nimhygiene):
{.pragma: inject.}
template parseSpecAux(fillResult: stmt) =
var ss = newStringStream(extractSpec(filename))
var p: TCfgParser
var p {.inject.}: TCfgParser
open(p, ss, filename, 1)
while true:
var e = next(p)
var e {.inject.} = next(p)
case e.kind
of cfgEof: break
of cfgSectionStart, cfgOption, cfgError:

View File

@@ -1,16 +1,19 @@
version 0.9.0
=============
- make templates hygienic by default: 'gensym', 'inject' pragmas
- make 'bind' default for templates and introduce 'mixin'
- use ``\`` for comment continuations
- ``final`` should be the default for objects
- implement "closure tuple consists of a single 'ref'" optimization
- implement for loop transformation for first class iterators
- make templates hygienic by default: 'gensym', 'inject' pragmas
- implicit deref for parameter matching
- ``final`` should be the default for objects
- optimize genericAssign in the code generator
- the lookup rules for generics really are too permissive
- fix remaining closure bugs:
- fix evals.nim with closures
- test evals.nim with closures
- what about macros with closures?
Bugs