92 lines
3.2 KiB
Nim

import
macros, log_output, scope_helpers, options, dynamic_scope_types
proc appenderIMPL[LogRecord, PropertyType](log: var LogRecord,
keyValuePair: ptr ScopeBindingBase[LogRecord]) =
type ActualType = ptr ScopeBinding[LogRecord, PropertyType]
# XXX: The use of `cast` here shouldn't be necessary. This is a normal explicit upcast.
let v = cast[ActualType](keyValuePair)
log.setProperty v.name, v.value
proc logAllDynamicProperties*[LogRecord](stream: typedesc, r: var LogRecord) =
# This proc is intended for internal use only
mixin tlsSlot
var frame = tlsSlot(stream)
while frame != nil:
for i in 0 ..< frame.bindingsCount:
let binding = frame.bindings[i]
binding.appender(r, binding)
frame = frame.prev
proc makeScopeBinding[T](LogRecord: typedesc,
name: string,
value: T): ScopeBinding[LogRecord, T] =
result.name = name
result.appender = appenderIMPL[LogRecord, T]
result.value = value
macro dynamicLogScopeIMPL*(stream: typedesc,
lexicalScopes: typed,
args: varargs[untyped]): untyped =
# XXX: open question: should we support overriding of dynamic props
# inside inner scopes. This will have some run-time overhead.
let body = args[^1]
args.del(args.len - 1)
if body.kind != nnkStmtList:
error "dynamicLogScope expects a block", body
var
makeScopeBinding = bindSym"makeScopeBinding"
bindingsVars = newTree(nnkStmtList)
bindingsArray = newTree(nnkBracket)
bindingsArraySym = genSym(nskLet, "bindings")
RecordType = genSym(nskType, "Record")
for name, value in assignments(args, acLogStatement):
var bindingVar = genSym(nskLet, name)
bindingsVars.add quote do:
let `bindingVar` = `makeScopeBinding`(`RecordType`, `name`, `value`)
bindingsArray.add newCall("unsafeAddr", bindingVar)
when defined(js):
bindingsArray = prefix(bindingsArray, "@")
let totalBindingVars = bindingsVars.len
result = quote:
var prevBindingFrame = tlsSlot(`stream`)
try:
type `RecordType` = Record(`stream`)
# All of the dynamic binding pairs are placed on the stack.
`bindingsVars`
# An array is created to hold pointers to them.
# This works, because of the common base type `ScopeBindingBase[LogRecord]`.
let `bindingsArraySym` = `bindingsArray`
# A `BindingFrame` object is also placed on the stack, holding
# meta-data about the array and a link to the previous BindingFrame.
let bindingFrame = BindingsFrame[`RecordType`](
prev: prevBindingFrame,
bindings: cast[BindingsArray[`RecordType`]](unsafeAddr `bindingsArraySym`),
bindingsCount: `totalBindingVars`)
# The address of the new BindingFrame is written to a TLS location.
tlsSlot(`stream`) = unsafeAddr(bindingFrame)
# XXX: In resumable functions, we need help from the compiler to let us
# intercept yields and resumes so we can restore our context.
`body`
finally:
# After the scope block has been executed, we restore the previous
# top BindingFrame.
tlsSlot(`stream`) = prevBindingFrame