A motivating example for the new bindSym behavior.

The example is a proof-of-concept logging library, allowing you to define
lexically-scoped environments where certain logging attributes are applied
automatically to all logging statements.

fixes tmacro1 (use of `bindSym` inside static blocks)
This commit is contained in:
Zahary Karadjov 2018-04-19 19:16:05 +03:00 • committed by Andreas Rumpf
commit 2b8bf8fc4a
5 changed files with 160 additions and 1 deletions

View file

@ -89,6 +89,7 @@ type
ambiguousSymbols*: IntSet # ids of all ambiguous symbols (cannot ambiguousSymbols*: IntSet # ids of all ambiguous symbols (cannot
# store this info in the syms themselves!) # store this info in the syms themselves!)
inGenericContext*: int # > 0 if we are in a generic type inGenericContext*: int # > 0 if we are in a generic type
inStaticContext*: int # > 0 if we are inside a static: block
inUnrolledContext*: int # > 0 if we are unrolling a loop inUnrolledContext*: int # > 0 if we are unrolling a loop
compilesContextId*: int # > 0 if we are in a ``compiles`` magic compilesContextId*: int # > 0 if we are in a ``compiles`` magic
compilesContextIdGenerator*: int compilesContextIdGenerator*: int

View file

@ -1780,6 +1780,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
let oldInGenericContext = c.inGenericContext let oldInGenericContext = c.inGenericContext
let oldInUnrolledContext = c.inUnrolledContext let oldInUnrolledContext = c.inUnrolledContext
let oldInGenericInst = c.inGenericInst let oldInGenericInst = c.inGenericInst
let oldInStaticContext = c.inStaticContext
let oldProcCon = c.p let oldProcCon = c.p
c.generics = @[] c.generics = @[]
var err: string var err: string
@ -1794,6 +1795,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
c.inGenericContext = oldInGenericContext c.inGenericContext = oldInGenericContext
c.inUnrolledContext = oldInUnrolledContext c.inUnrolledContext = oldInUnrolledContext
c.inGenericInst = oldInGenericInst c.inGenericInst = oldInGenericInst
c.inStaticContext = oldInStaticContext
c.p = oldProcCon c.p = oldProcCon
msgs.setInfoContextLen(oldContextLen) msgs.setInfoContextLen(oldContextLen)
setLen(c.graph.owners, oldOwnerLen) setLen(c.graph.owners, oldOwnerLen)

View file

@ -199,7 +199,7 @@ proc semBindSym(c: PContext, n: PNode): PNode =
if s != nil: if s != nil:
# we need to mark all symbols: # we need to mark all symbols:
var sc = symChoice(c, id, s, TSymChoiceRule(isMixin.intVal)) var sc = symChoice(c, id, s, TSymChoiceRule(isMixin.intVal))
if not getCurrOwner(c).isCompileTimeProc: if not (c.inStaticContext > 0 or getCurrOwner(c).isCompileTimeProc):
# inside regular code, bindSym resolves to the sym-choice # inside regular code, bindSym resolves to the sym-choice
# nodes (see tinspectsymbol) # nodes (see tinspectsymbol)
return sc return sc

View file

@ -1754,7 +1754,9 @@ proc semPragmaBlock(c: PContext, n: PNode): PNode =
proc semStaticStmt(c: PContext, n: PNode): PNode = proc semStaticStmt(c: PContext, n: PNode): PNode =
#echo "semStaticStmt" #echo "semStaticStmt"
#writeStackTrace() #writeStackTrace()
inc c.inStaticContext
let a = semStmt(c, n.sons[0]) let a = semStmt(c, n.sons[0])
dec c.inStaticContext
n.sons[0] = a n.sons[0] = a
evalStaticStmt(c.module, c.cache, c.graph.config, a, c.p.owner) evalStaticStmt(c.module, c.cache, c.graph.config, a, c.p.owner)
result = newNodeI(nkDiscardStmt, n.info, 1) result = newNodeI(nkDiscardStmt, n.info, 1)

View file

@ -0,0 +1,154 @@
discard """
output: '''
main started: a=10, b=inner-b, c=10, d=some-d, x=16, z=20
exiting: a=12, b=overriden-b, c=100, msg=bye bye, x=16
'''
"""
import macros, tables
template scopeHolder =
0 # scope revision number
type
BindingsSet = Table[string, NimNode]
proc actualBody(n: NimNode): NimNode =
# skip over the double StmtList node introduced in `mergeScopes`
result = n.body
if result.kind == nnkStmtList and result[0].kind == nnkStmtList:
result = result[0]
iterator bindings(n: NimNode, skip = 0): (string, NimNode) =
for i in skip ..< n.len:
let child = n[i]
if child.kind in {nnkAsgn, nnkExprEqExpr}:
let name = $child[0]
let value = child[1]
yield (name, value)
proc scopeRevision(scopeHolder: NimNode): int =
# get the revision number from a scopeHolder sym
assert scopeHolder.kind == nnkSym
var revisionNode = scopeHolder.getImpl.actualBody[0]
result = int(revisionNode.intVal)
proc lastScopeHolder(scopeHolders: NimNode): NimNode =
# get the most recent scopeHolder from a symChoice node
if scopeHolders.kind in {nnkClosedSymChoice, nnkOpenSymChoice}:
var bestScopeRev = 0
assert scopeHolders.len > 0
for scope in scopeHolders:
let rev = scope.scopeRevision
if result == nil or rev > bestScopeRev:
result = scope
bestScopeRev = rev
else:
result = scopeHolders
assert result.kind == nnkSym
macro mergeScopes(scopeHolders: typed, newBindings: untyped): untyped =
var
bestScope = scopeHolders.lastScopeHolder
bestScopeRev = bestScope.scopeRevision
var finalBindings = initTable[string, NimNode]()
for k, v in bindings(bestScope.getImpl.actualBody, skip = 1):
finalBindings[k] = v
for k, v in bindings(newBindings):
finalBindings[k] = v
var newScopeDefinition = newStmtList(newLit(bestScopeRev + 1))
for k, v in finalBindings:
newScopeDefinition.add newAssignment(newIdentNode(k), v)
result = quote:
template scopeHolder = `newScopeDefinition`
template scope(newBindings: untyped) {.dirty.} =
mergeScopes(bindSym"scopeHolder", newBindings)
type
TextLogRecord = object
line: string
StdoutLogRecord = object
template setProperty(r: var TextLogRecord, key: string, val: string, isFirst: bool) =
if not first: r.line.add ", "
r.line.add key
r.line.add "="
r.line.add val
template setEventName(r: var StdoutLogRecord, name: string) =
stdout.write(name & ": ")
template setProperty(r: var StdoutLogRecord, key: string, val: auto, isFirst: bool) =
when not isFirst: stdout.write ", "
stdout.write key
stdout.write "="
stdout.write $val
template flushRecord(r: var StdoutLogRecord) =
stdout.write "\n"
stdout.flushFile
macro logImpl(scopeHolders: typed,
logStmtProps: varargs[untyped]): untyped =
let lexicalScope = scopeHolders.lastScopeHolder.getImpl.actualBody
var finalBindings = initOrderedTable[string, NimNode]()
for k, v in bindings(lexicalScope, skip = 1):
finalBindings[k] = v
for k, v in bindings(logStmtProps, skip = 1):
finalBindings[k] = v
finalBindings.sort(system.cmp)
let eventName = logStmtProps[0]
assert eventName.kind in {nnkStrLit}
let record = genSym(nskVar, "record")
result = quote:
var `record`: StdoutLogRecord
setEventName(`record`, `eventName`)
var isFirst = true
for k, v in finalBindings:
result.add newCall(newIdentNode"setProperty",
record, newLit(k), v, newLit(isFirst))
isFirst = false
result.add newCall(newIdentNode"flushRecord", record)
template log(props: varargs[untyped]) {.dirty.} =
logImpl(bindSym"scopeHolder", props)
scope:
a = 12
b = "original-b"
scope:
x = 16
b = "overriden-b"
scope:
c = 100
proc main =
scope:
c = 10
scope:
z = 20
log("main started", a = 10, b = "inner-b", d = "some-d")
main()
log("exiting", msg = "bye bye")