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
stderr functions look for continuation marks named
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 exists
ccmark– unbinds a continuation mark, restoring the previous value if one exists
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!