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=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")
|