| 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 | |
| 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 | |
| 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 | |
| 60 | /* @MC_ACT(stmt)@ |
| 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 | */ |
| 76 | #define MC_ACT(stmt) if (1) { stmt; } else |
| 77 | |
| 78 | /* @MC_LABEL(tag)@ |
| 79 | * @MC_GOTO(tag);@ |
| 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@. |
| 87 | */ |
| 88 | #define MC_LABEL(tag) MCTRL__LABEL(tag): |
| 89 | #define MC_GOTO(tag) MC_ACT(goto MCTRL__LABEL(tag)) |
| 90 | |
| 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@ |
| 106 | * |
| 107 | * Execute @stmt@ and then @body@. |
| 108 | */ |
| 109 | #define MC_BEFORE(tag, stmt) \ |
| 110 | MC_ACT(stmt; MC_GOTO(tag##__body)) \ |
| 111 | MC_LABEL(tag##__body) |
| 112 | |
| 113 | /* @MC_AFTER(tag, stmt) body@ |
| 114 | * |
| 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@. |
| 118 | */ |
| 119 | #define MC_AFTER(tag, stmt) \ |
| 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) |
| 125 | |
| 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);@ |
| 139 | * |
| 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 |
| 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 | */ |
| 158 | #define MC_WRAP(tag, before, onend, onbreak) \ |
| 159 | MC_ACT(before; MC_GOTO(tag##__body)) \ |
| 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 | |
| 168 | /* @MC_FINALLY(tag, cleanup) body@ |
| 169 | * |
| 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. |
| 173 | * |
| 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. |
| 177 | */ |
| 178 | #define MC_FINALLY(tag, cleanup) \ |
| 179 | MP_WRAP(tag##__final, { ; } cleanup, { cleanup break; }) |
| 180 | |
| 181 | /* @MC_DECL(tag, decl) body@ |
| 182 | * |
| 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++. |
| 188 | */ |
| 189 | #if __STDC_VERSION__ >= 199901 || defined(__cplusplus) |
| 190 | # define MC_DECL(tag, decl) \ |
| 191 | for (decl;;) \ |
| 192 | MC_GOTO(tag##__body) \ |
| 193 | MC_LABEL(tag##__exit) MC_ACT(break) \ |
| 194 | for (;;) \ |
| 195 | MC_GOTO(tag##__exit) \ |
| 196 | MC_LABEL(tag##__body) |
| 197 | #endif |
| 198 | |
| 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 | |
| 254 | /*----- That's all, folks -------------------------------------------------*/ |
| 255 | |
| 256 | #ifdef __cplusplus |
| 257 | } |
| 258 | #endif |
| 259 | |
| 260 | #endif |