1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
discard """
output: '''
main started: a=10, b=inner-b, c=10, d=some-d, x=16, z=20
exiting: a=12, b=overridden-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 {.redefine.} = `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 = "overridden-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")
|