CX Framework
Cross-platform C utility framework
Loading...
Searching...
No Matches
pblock.h
Go to the documentation of this file.
1#pragma once
2
5
54
55#include <cx/meta/block.h>
57#include <setjmp.h>
58
59enum _pblock_unwind_const {
60 _pblock_unwind_top = 0
61};
62
65typedef struct _pblock_jmp_buf_node _pblock_jmp_buf_node;
66typedef struct _pblock_jmp_buf_node {
67 _pblock_jmp_buf_node *next;
68 jmp_buf buf;
69 volatile int target;
70} _pblock_jmp_buf_node;
71
72typedef _pblock_jmp_buf_node _pblock_jmp_buf[1];
73#define BLOCK_JMPBUF_INIT {[0] = {.target = 0}}
74
75_meta_inline /*noreturn*/ void _pbLongjmp(_pblock_jmp_buf_node *node, int val)
76{
77 node->target = val;
78 longjmp(node->buf, 1);
79}
80
124#define pblock \
125 _blkStart \
126 _inhibitReturn \
127 /* set to true during the unwinding process */ \
128 _blkDef(volatile bool _pblock_unwind = false) \
129 _blkFull(/* BEFORE */ volatile int _pblock_target = 0, true, \
130 /* After we exit the block through whatever means, if we are unwinding, try to unwind the next level. \
131 * Since this loop is above the one that defines _pblock_unwind_top, the jump buffer here points to the \
132 * one set by the block that encloses this one and we'll jump back into it, giving it the ability to \
133 * run any PBLOCK_AFTER cleanup and continue the chain. \
134 * In the outermost block, _pblock_unwind_top will instead be 0 from the global enum, and pblockUnwind \
135 * will do nothing. \
136 * If _pblock_target reaches 0, this will also exit the loop and continue execution as normal. */ \
137 /* AFTER */ (_pblock_unwind ? \
138 pblockUnwind(_pblock_target <= 0 ? _pblock_target : _pblock_target - 1) : nop_stmt)) \
139 /* allocate a jump buffer on the stack for this level of recursion */ \
140 _blkDef(_pblock_jmp_buf _pblock_unwind_top = tokeval(BLOCK_JMPBUF_INIT)) \
141 /* set the longjump target to be within the inntermost loop and coerce the return value to 1 or 0 */ \
142 switch(setjmp(_pblock_unwind_top[0].buf)) \
143 /* case 2 doesn't actually exist, it serves as a place to hang an if-else construct so we can define \
144 two case labels here instead of only one - case 0 for the user block to execute, and a default \
145 case label that acts as a fallback for case 1 not being defined, which can happen if the user does \
146 not include a PBLOCK_AFTER label. */ \
147 case 2: if (0) { \
148 default: \
149 /* if there isn't a PBLOCK_AFTER but we got here as a longjmp target, we need to take care of \
150 * setting up the variable for unwinding, otherwise the AFTER blocks will fail to jump to the \
151 * next target in the chain and we'll stop unwinding early. */ \
152 _pblock_target = _pblock_unwind_top[0].target; \
153 _pblock_unwind = !!_pblock_target; \
154 } else \
155 /* This is the case for when we get here the first time, right after calling setjmp. The user-provided \
156 * block slots in directly after this label. */ \
157 case 0:
158
159
160_meta_inline void _pblockUnwind(void *top, int cond)
161{
162 if (cond && top) _pbLongjmp(top, cond);
163}
164
194#define pblockUnwind(num) _pblockUnwind((void*)_pblock_unwind_top, (num))
195
226#define PBLOCK_AFTER \
227 if (0) { \
228 /* Even though this case 1 label is placed into the user-defined block, it technically exists within \
229 * the switch statement at the end of the pblock macro. \
230 * When the user provides a PBLOCK_AFTER target, it defines this case 1 which overrides the default \
231 * case label (the one that's hidden in the if(0) after the case 2:) and comes here instead. */ \
232 case 1: \
233 _pblock_target = _pblock_unwind_top[0].target; \
234 _pblock_unwind = !!_pblock_target; \
235 /* Execution falls through to the statement below. */ \
236 } \
237 /* This odd looking construct exists to swallow the ':' after the fake PBLOCK_AFTER label. */ \
238 switch(0) case 0
239
Block wrapping macros for automatic resource management.
Macros for suppressing compiler warnings.