chiark / gitweb /
@@@ remote works?
[mLib] / utils / control.h
CommitLineData
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 178#define MC_FINALLY(tag, cleanup) \
c91413e6 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