Skip to content

Instantly share code, notes, and snippets.

@Skrylar
Created May 8, 2020 00:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Skrylar/7b91149530800117300a54ac59df9b60 to your computer and use it in GitHub Desktop.
Save Skrylar/7b91149530800117300a54ac59df9b60 to your computer and use it in GitHub Desktop.
I wanted to know how `chronicle` 's stack-based parameter system for logs could work. So I pulled this out of a description of it on their README.
#- = Making a stack-based parameter system
#-
#- I wanted to know how `chronicle` 's stack-based parameter system for
#- logs could work. So I pulled this out of a description of it on their
#- README. No spoilers were read beforehand.
#-
#- Here's how it works:
#-
#- . Marker objects are defined to hold information relevant from that
#- level of the stack downward.
#- . Markers are pushed to a thread-local global, and point to their
#- predecessors.
#- . A `defer`-red block pops these markers when the proc is exiting.
#- . A block of code (the "machinery") is added to perform the above
#- steps in functions that need to be surrounded in markers.
#-
#- == Type definitions
type
#- Markers are things which live on the stack and hold some constant
#- data that might be needed further down the stack.
Marker = object
previous: ptr Marker
wot: string
#- Tip of the marker stack. Should be thread-local.
var topmost: ptr Marker # threadlocal
#- == Marking and Unmarking
#- `mark` will place a marker on top of the stack. It will also set a pointer
#- so we can continue walking the stack later.
proc mark(self: var Marker) {.inline.} =
self.previous = topmost
topmost = addr self
#- `unmark` does some debug-time assertions to make sure nothing stupid
#- has happened, then pops a marker from the stack. Ideally this is shoved
#- in a `defer` statement or entirely hidden by a macro.
proc unmark(self: var Marker) {.inline.} =
assert(topmost == addr self)
assert(topmost != nil)
topmost = topmost.previous
#- == Procs showing off manual stack machinery
proc doneit() =
var muppet: Marker
muppet.wot = "confound-it!"
mark(muppet)
defer: unmark(muppet)
echo "Currently the word of the day is ", topmost.wot
proc doit() =
var muppet: Marker
muppet.wot = "shoop"
mark(muppet)
defer: unmark(muppet)
doneit()
echo "Currently the word of the day is ", topmost.wot
doit()
#- == But thats tedious so make a macro do it
import macros
#- To make this a macro we have to:
#-
#- . Capture the marker creation, config, and destruction deferral as
#- a block of AST.
#- . Generate an inaccessible symbol to hold the marker until destruction.
#- . Inject the marker code at the start of a function.
#-
#- NOTE: You could argue that the marker could go inside a `block` so
#- it stays out of scope entirely. I suspect this would do something
#- stupid: the `defer` would kill the marker when the temporary block
#- exists. That is fine for many things but explicitly not what we want
#- here.
macro do_it_live(wot: string; bloc: untyped): untyped =
let sym = gensym(nskVar, "marker")
# create payload that creates marker, configures, pushes, and
# ultimately pops it
let injection = nnkStmtList.newTree(
nnkVarSection.newTree(
nnkIdentDefs.newTree(
sym,
newIdentNode("Marker"),
newEmptyNode()
)
),
nnkAsgn.newTree(
nnkDotExpr.newTree(
sym,
newIdentNode("wot")
),
wot
),
nnkCall.newTree(
newIdentNode("mark"),
sym
),
nnkDefer.newTree(
nnkStmtList.newTree(
nnkCall.newTree(
newIdentNode("unmark"),
sym
)
)
)
)
# inject the payload
bloc[6].insert(0, injection)
return bloc
#- This version has all the same machinery, but its hidden by our macro.
proc makeamacrodoit() {.do_it_live: "sauce".} =
echo "Currently the word of the day is ", topmost.wot
makeamacrodoit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment