Commit | Line | Data |
---|---|---|
e6591bec MW |
1 | /* -*-c-*- |
2 | * | |
3 | * Control operators, after Simon Tatham | |
4 | * | |
5 | * (c) 2022 Straylight/Edgeware | |
6 | */ | |
7 | ||
8 | /*----- Licensing notice --------------------------------------------------* | |
9 | * | |
10 | * This file is part of the mLib utilities library. | |
11 | * | |
12 | * mLib is free software: you can redistribute it and/or modify it under | |
13 | * the terms of the GNU Library General Public License as published by | |
14 | * the Free Software Foundation; either version 2 of the License, or (at | |
15 | * your option) any later version. | |
16 | * | |
17 | * mLib is distributed in the hope that it will be useful, but WITHOUT | |
18 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
19 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | |
20 | * License for more details. | |
21 | * | |
22 | * You should have received a copy of the GNU Library General Public | |
23 | * License along with mLib. If not, write to the Free Software | |
24 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |
25 | * USA. | |
26 | */ | |
27 | ||
28 | #ifndef MLIB_CONTROL_H | |
29 | #define MLIB_CONTROL_H | |
30 | ||
31 | #ifdef __cplusplus | |
32 | extern "C" { | |
33 | #endif | |
34 | ||
b64eb60f MW |
35 | /*----- Notes on the control operator machinery ---------------------------* |
36 | * | |
37 | * These macros owe an obvious and immense debt to Simon Tatham's article | |
38 | * `Metaprogramming custom control structures in C', available from | |
39 | * https://www.chiark.greenend.org.uk/~sgtatham/mp/. The basic tricks are | |
40 | * all Tatham's, as are most of the provided operators. The focus on | |
41 | * @MC_ACT@ as a significant primitive is probably my main original | |
42 | * contribution. | |
43 | */ | |
44 | ||
e6591bec MW |
45 | /*----- Header files ------------------------------------------------------*/ |
46 | ||
47 | #ifndef MLIB_MACROS_H | |
48 | # include "macros.h" | |
49 | #endif | |
50 | ||
51 | /*----- Macros provided ---------------------------------------------------*/ | |
52 | ||
53 | /* @MCTRL__LABEL(tag)@ * | |
54 | * | |
55 | * Expand to a plausibly unique label based on the current line number and | |
56 | * the @tag@. | |
57 | */ | |
58 | #define MCTRL__LABEL(tag) GLUE(_mctrl__##tag##__, __LINE__) | |
59 | ||
2edecb46 | 60 | /* @MC_ACT(stmt)@ |
2edecb46 MW |
61 | * |
62 | * @MC_ACT@ is the main `trick' for constructing these flow-control | |
63 | * operators. It wraps up a statement as what we call an `action'. Actions | |
64 | * can be concatenated together to form a valid statement head, i.e., a | |
65 | * sequence of actions can be followed by a statement, called the `body', to | |
66 | * form a single syntactic statement. The body can be simply `;', so an | |
67 | * action can be treated as a simple statement. However, if an action | |
68 | * sequence is executed, only the first statement is executed. | |
69 | * | |
70 | * Actions can be labelled, e.g., using @MC_LABEL@, just like statements. If | |
71 | * control is passed to a label, e.g., by @MC_GOTO@, then the statement | |
72 | * within the following action (only) is executed; the normal flow of control | |
73 | * will then be to the statement following the containing action sequence and | |
74 | * its body. | |
75 | */ | |
491f4344 | 76 | #define MC_ACT(stmt) if (1) { stmt; } else |
2edecb46 MW |
77 | |
78 | /* @MC_LABEL(tag)@ | |
b64eb60f | 79 | * @MC_GOTO(tag);@ |
2edecb46 MW |
80 | * |
81 | * @MC_LABEL@ just establishes a label which can be invoked (only) from the | |
82 | * same top-level macro; and @MC_GOTO@ transfers control to it. | |
83 | * | |
84 | * The @MC_GOTO@ macro is special in that it can be used either as a plain | |
85 | * statement, followed by a semicolon in the usual way, or as a prefix | |
86 | * action in its own right, in place of @MC_ACT@. | |
e6591bec | 87 | */ |
2edecb46 | 88 | #define MC_LABEL(tag) MCTRL__LABEL(tag): |
491f4344 | 89 | #define MC_GOTO(tag) MC_ACT(goto MCTRL__LABEL(tag)) |
e6591bec | 90 | |
b64eb60f MW |
91 | /* @MC_TARGET(tag, stmt) body@ |
92 | * @MC_GOTARGET(tag);@ | |
93 | * | |
94 | * Executing @TARGET@ statement executes the @body@, as if the @TARGET@ | |
95 | * weren't there. Executing a @GOTARGET@ transfers control to the @stmt@ | |
96 | * (but not normally the @body@). In either case, the normal flow of control | |
97 | * is then to the following statement. | |
98 | */ | |
99 | #define MC_TARGET(tag, stmt) \ | |
100 | MC_GOTO(tag##__body) \ | |
101 | MC_LABEL(tag##__tgt) MC_ACT(stmt) \ | |
102 | MC_LABEL(tag##__body) | |
103 | #define MC_GOTARGET(tag) do MC_GOTO(tag##__tgt); while (0) | |
104 | ||
105 | /* @MC_BEFORE(tag, stmt) body@ | |
e6591bec | 106 | * |
b64eb60f | 107 | * Execute @stmt@ and then @body@. |
e6591bec | 108 | */ |
b64eb60f | 109 | #define MC_BEFORE(tag, stmt) \ |
491f4344 | 110 | MC_ACT(stmt; MC_GOTO(tag##__body)) \ |
2edecb46 | 111 | MC_LABEL(tag##__body) |
e6591bec | 112 | |
b64eb60f | 113 | /* @MC_AFTER(tag, stmt) body@ |
e6591bec | 114 | * |
b64eb60f MW |
115 | * Execute @body@ and then @stmt@. If @body@ invokes @break@ then control |
116 | * immediately transfers to the statement following @MC_AFTER@. If @body | |
117 | * invokes @continue@, then control returns to @stmt@. | |
e6591bec | 118 | */ |
b64eb60f | 119 | #define MC_AFTER(tag, stmt) \ |
2edecb46 MW |
120 | MC_GOTO(tag##__body) \ |
121 | MC_LABEL(tag##__end) MC_ACT(stmt) \ | |
122 | for (;;) \ | |
123 | MC_GOTO(tag##__end) \ | |
124 | MC_LABEL(tag##__body) | |
e6591bec | 125 | |
b64eb60f MW |
126 | /* @MC_DOWHILE(tag, cond) body@ |
127 | * | |
128 | * Repeatedly execute @body@ until @cond@ evaluates to zero. The @body@ is | |
129 | * executed once before @cond@ is checked the first time. The @break@ and | |
130 | * @continue@ statements work within @body@ as one would expect. | |
131 | */ | |
132 | #define MC_DOWHILE(tag, cond) \ | |
133 | MC_GOTO(tag##__body) \ | |
134 | while (cond) \ | |
135 | MC_LABEL(tag##__body) | |
136 | ||
137 | /* @MC_ALLOWELSE(tag) apodosis_body [else haeresis_body]@ | |
138 | * @MC_GOELSE(tag);@ | |
e6591bec | 139 | * |
b64eb60f MW |
140 | * Executing @MC_ALLOWELSE@ executes @apodosis_body@, but not |
141 | * @haeresis_body@. If @MC_GOELSE(tag)@ is executed, then control continues | |
142 | * from @haeresis_body@. | |
143 | */ | |
144 | #define MC_ALLOWELSE(tag) \ | |
145 | MC_GOTO(tag##__body) \ | |
146 | MC_LABEL(tag##__else) if (0) \ | |
147 | MC_LABEL(tag##__body) | |
148 | #define MC_GOELSE(tag) do MC_GOTO(tag##__else); while (0) | |
149 | ||
150 | /* @MC_WRAP(tag, before, onend, onbreak) body@ | |
151 | * | |
152 | * Execute the @before@ statement, followed by @body@. If @body@ invokes | |
153 | * @break@, then @onbreak@ is immediately executed; if @body@ completes | |
e6591bec MW |
154 | * normally, or invokes @continue@ then @onend@ is immediately executed. |
155 | * Any @break@ and @continue@ in the @before@, @onend@, and @onbreak@ | |
156 | * statements behave as one would expect from their context. | |
157 | */ | |
b64eb60f | 158 | #define MC_WRAP(tag, before, onend, onbreak) \ |
491f4344 | 159 | MC_ACT(before; MC_GOTO(tag##__body)) \ |
2edecb46 MW |
160 | MC_LABEL(tag##__end) MC_ACT(onend) \ |
161 | MC_LABEL(tag##__brk) MC_ACT(onbreak) \ | |
162 | for (;;) \ | |
163 | MC_GOTO(tag##__brk) \ | |
164 | for (;;) \ | |
165 | MC_GOTO(tag##__end) \ | |
166 | MC_LABEL(tag##__body) | |
167 | ||
b64eb60f | 168 | /* @MC_FINALLY(tag, cleanup) body@ |
e6591bec | 169 | * |
b64eb60f MW |
170 | * Execute @cleanup@ when @body@ completes or ends with @break@. In the |
171 | * latter case, propagate the @break@ to the enclosing context -- for which | |
172 | * it must be syntactically appropriate. | |
e6591bec | 173 | * |
b64eb60f MW |
174 | * The @cleanup@ code is duplicated. If it arrange to have private long-term |
175 | * state, e.g, by declaring @static@ variables, then the two copies will not | |
176 | * share the same state, so probably don't do this. | |
e6591bec | 177 | */ |
b64eb60f MW |
178 | #define MC_FINALLY(tag, cleanup) \ |
179 | MP_WRAP(tag##__final, { ; } cleanup, { cleanup break; }) | |
e6591bec | 180 | |
b64eb60f | 181 | /* @MC_DECL(tag, decl) body@ |
e6591bec | 182 | * |
b64eb60f MW |
183 | * Execute @body@ with @decl@ in scope. If @body@ completes or invokes |
184 | * @continue@ then control continues with the statement following @MC_DECL@; | |
185 | * if it invokes @break@ then it will be restarted without leaving the scope | |
186 | * of @decl@. Internally, this uses @for@, so it only works in C99 or later, | |
187 | * or C++. | |
e6591bec MW |
188 | */ |
189 | #if __STDC_VERSION__ >= 199901 || defined(__cplusplus) | |
b64eb60f | 190 | # define MC_DECL(tag, decl) \ |
2edecb46 MW |
191 | for (decl;;) \ |
192 | MC_GOTO(tag##__body) \ | |
b64eb60f | 193 | MC_LABEL(tag##__exit) MC_ACT(break) \ |
2edecb46 | 194 | for (;;) \ |
b64eb60f | 195 | MC_GOTO(tag##__exit) \ |
2edecb46 | 196 | MC_LABEL(tag##__body) |
e6591bec MW |
197 | #endif |
198 | ||
b64eb60f MW |
199 | /* @MC_LOOPELSE(tag, head) loop_body [else else_body]@ |
200 | * | |
201 | * Python-like looping with optional @else@ clause. @head loop_body@ must be | |
202 | * a syntactically valid @for@, @while@, or @MC_DOWHILE@ loop; if the loop | |
203 | * exits because of @break@ then control continues in the usual way; | |
204 | * otherwise, the @else_body@ (if any) is executed. | |
205 | */ | |
206 | #define MC_LOOPELSE(tag, head) \ | |
207 | MC_TARGET(tag##__exit, { ; }) \ | |
208 | MC_ALLOWELSE(tag##__else) \ | |
209 | MC_AFTER(tag##__after, { MC_GOELSE(tag##__else); }) \ | |
210 | head \ | |
211 | MC_WRAP(tag##__body, { ; }, { ; }, { MC_GOTARGET(tag##__exit); }) | |
212 | ||
213 | /* @MC_LOOPBETWEEN(tag, setup, cond, step) loop_body [else else_body]@ | |
214 | * | |
215 | * This is essentially a @for@ loop with a twist. The @setup@, @cond@, and | |
216 | * @step@ arguments are the parts of the @for@ head clause; because of the | |
217 | * limitations of the C macro syntax, they're separated by commas rather than | |
218 | * semicolons. | |
219 | * | |
220 | * The twist is that, once the @loop_body@ has finished, the @step@ | |
221 | * expression evaluated, and the @cond@ evaluated and determined to be | |
222 | * nonzero, the @else_body@ (if any) is executed before re-entering the | |
223 | * @loop_body@. This makes it a useful place to insert any kind of | |
224 | * interstitial material, e.g., printing commas between list items. | |
225 | * | |
226 | * The @cond@ is textually duplicated. You'll get some code bloat if the | |
227 | * condition is very complex. If it somehow arranges to have private | |
228 | * long-term state (e.g., as a result of declaring static variables inside | |
229 | * GCC statement expressions), then the two copies will not share this state, | |
230 | * so probably don't do this. | |
231 | * | |
232 | * Note that by the time that the @else_body@ is executed, the decision has | |
233 | * already been made that another iteration will be performed, and, in | |
234 | * particular, the @step@ has occurred. The @else_body@ is therefore looking | |
235 | * at the next item to be processed, not the item that has just finished | |
236 | * being processed. | |
237 | */ | |
238 | #define MC_LOOPBETWEEN(tag, setup, cond, step) \ | |
239 | for (setup;;) \ | |
240 | if (!(cond)) break; else \ | |
241 | MC_TARGET(tag##__exit, { break; }) \ | |
242 | for (;;) \ | |
243 | MC_WRAP(tag##__tailwrap, { ; }, \ | |
244 | { ; }, \ | |
245 | { MC_GOTARGET(tag##__exit); }) \ | |
246 | MC_ALLOWELSE(tag##__tail) \ | |
247 | MC_WRAP(tag##__bodywrap, { ; }, \ | |
248 | { if ((step), !(cond)) \ | |
249 | MC_GOTARGET(tag##__exit); \ | |
250 | else \ | |
251 | MC_GOELSE(tag##__tail); }, \ | |
252 | { MC_GOTARGET(tag##__exit); }) | |
253 | ||
e6591bec MW |
254 | /*----- That's all, folks -------------------------------------------------*/ |
255 | ||
256 | #ifdef __cplusplus | |
257 | } | |
258 | #endif | |
259 | ||
260 | #endif |