mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-28 08:54:53 +00:00
155 lines
3.8 KiB
Nim
155 lines
3.8 KiB
Nim
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")
|
|
|