Continuation marks in Arcueid
In order to generalize the notion of call-w/std(in|out)
, Arcueid provides a more general call-w/cmark
macro that allows one to bind arbitrary continuation marks when a thunk is called. Since Arcueid’s built-in stdin
, stdout
, and stderr
functions look for continuation marks named stdin-fd
, stdout-fd
, and stderr-fd
respectively, using call-w/cmark with these continuation marks suffices to implement them. If no marks are bound to these names, then the global variables with these values are used, these being normally bound to stdin, stdout, and stderr ports that are created when the system initializes.
Arcueid’s implementation of continuation marks is very simple. Three functions are provided:
cmark
– retrieves the binding of an existing continuation mark if one is available.scmark
– binds a continuation mark, covering the previous value if one existsccmark
– unbinds a continuation mark, restoring the previous value if one exists
The scmark
and ccmark
functions should never be used outside of the provided call-w/cmark
macro. This itself is a wrapper around dynamic-wind
, ensuring that the continuation marks created for a thunk are bound when the thunk is entered, and unbound when the thunk is exited. The definition is very simple:
(mac call-w/cmark (thunk . cmarks) `(dynamic-wind (fn () ,@(map1 [list 'scmark car._ cadr._] (pair cmarks))) ,thunk (fn () ,@(map1 [list 'ccmark car._] (pair cmarks)))))
As one can see, it is just a call to dynamic-wind
. The before thunk uses scmark
to bind all the specified continuation marks, and the after thunk uses ccmark
to unbind all continuation marks.
Every thread has a hash table that stores these continuation marks. New threads receive a flattened copy of their parent’s continuation marks as they existed at the time the parent spawned it. Values bound to a thread’s continuation mark table in this way cannot be removed by the child thread.
It looks like dynamic-wind
is seeing much more use as a primitive than expected!