Created
May 8, 2020 00:07
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#- = 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