From 2edecb46edfcdc4b25c9d12bcfe23a5d66ec3f33 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Tue, 25 Apr 2023 01:21:11 +0100 Subject: [PATCH] @@@ major overhaul, new primitives --- utils/control.h | 128 +++++++++++++++++++++++++------------------------ utils/t/control-test.c | 90 +++++++++++++++++++++++++++------- 2 files changed, 137 insertions(+), 81 deletions(-) diff --git a/utils/control.h b/utils/control.h index fdc7125..77d985a 100644 --- a/utils/control.h +++ b/utils/control.h @@ -47,28 +47,47 @@ */ #define MCTRL__LABEL(tag) GLUE(_mctrl__##tag##__, __LINE__) -/* @FIRSTBRANCH(tag_0) stmt_0@ - * [@MIDBRANCH(tag_i) stmt_i ...@] - * @LASTBRANCH(tag_n) stmt_n@ - * @GOBRANCH(tag);@ - * - * If control enters at the top, then only is executed, followed by - * the statement after. Following @GOBRANCH(tag)@, control continues - * from the correspondingly tagged statement, and continues with the - * following statement again. +/* @MC_ACT(stmt)@ + * @MC_PASS@ + * + * @MC_ACT@ is the main `trick' for constructing these flow-control + * operators. It wraps up a statement as what we call an `action'. Actions + * can be concatenated together to form a valid statement head, i.e., a + * sequence of actions can be followed by a statement, called the `body', to + * form a single syntactic statement. The body can be simply `;', so an + * action can be treated as a simple statement. However, if an action + * sequence is executed, only the first statement is executed. + * + * Actions can be labelled, e.g., using @MC_LABEL@, just like statements. If + * control is passed to a label, e.g., by @MC_GOTO@, then the statement + * within the following action (only) is executed; the normal flow of control + * will then be to the statement following the containing action sequence and + * its body. + */ +#define MC_ACT(stmt) if (1) stmt else +#define MC_PASS MC_ACT(;) + + +/* @MC_LABEL(tag)@ + * @MC_GOTO(tag)@ + * + * @MC_LABEL@ just establishes a label which can be invoked (only) from the + * same top-level macro; and @MC_GOTO@ transfers control to it. + * + * The @MC_GOTO@ macro is special in that it can be used either as a plain + * statement, followed by a semicolon in the usual way, or as a prefix + * action in its own right, in place of @MC_ACT@. */ -#define FIRSTBRANCH(tag) if (1) { goto MCTRL__LABEL(tag); MCTRL__LABEL(tag): -#define MIDBRANCH(tag) } else if (0) MCTRL__LABEL(tag): { -#define LASTBRANCH(tag) } else MCTRL__LABEL(tag): -#define GOBRANCH(tag) goto MCTRL__LABEL(tag) +#define MC_LABEL(tag) MCTRL__LABEL(tag): +#define MC_GOTO(tag) MC_ACT({ goto MCTRL__LABEL(tag); }) /* @BEFORE(tag, stmt_0) stmt_1@ * * Execute @stmt_0@ and then @stmt_1@. */ #define BEFORE(tag, stmt) \ - if (1) { stmt goto MCTRL__LABEL(tag##__before_body); } \ - else MCTRL__LABEL(tag##__before_body): + MC_ACT({ stmt MC_GOTO(tag##__body); }) \ + MC_LABEL(tag##__body) /* @AFTER(tag, stmt_0) stmt_1@ * @@ -77,11 +96,11 @@ * either invokes @continue@, then control returns to @stmt_0@. */ #define AFTER(tag, stmt) \ - if (1) goto MCTRL__LABEL(tag##__after_body); \ - else if (1) { MCTRL__LABEL(tag##__after_end): stmt } \ - else for (;;) \ - if (1) goto MCTRL__LABEL(tag##__after_end); \ - else MCTRL__LABEL(tag##__after_body): + MC_GOTO(tag##__body) \ + MC_LABEL(tag##__end) MC_ACT(stmt) \ + for (;;) \ + MC_GOTO(tag##__end) \ + MC_LABEL(tag##__body) /* @WRAP(tag, before, onend, onbreak) stmt@ * @@ -92,44 +111,26 @@ * statements behave as one would expect from their context. */ #define WRAP(tag, before, onend, onbreak) \ - if (1) { before goto MCTRL__LABEL(tag##__wrap_body); } \ - else if (1) { MCTRL__LABEL(tag##__wrap_end): onend } \ - else if (1) { MCTRL__LABEL(tag##__wrap_break): onbreak } \ - else for (;;) \ - if (1) goto MCTRL__LABEL(tag##__wrap_break); \ - else for (;;) \ - if (1) goto MCTRL__LABEL(tag##__wrap_end); \ - else MCTRL__LABEL(tag##__wrap_body): - -/* @ALLOWELSE(tag, before, onend, onbreak) stmt_0 [else stmt_1]@ + MC_ACT({ before MC_GOTO(tag##__body); }) \ + MC_LABEL(tag##__end) MC_ACT(onend) \ + MC_LABEL(tag##__brk) MC_ACT(onbreak) \ + for (;;) \ + MC_GOTO(tag##__brk) \ + for (;;) \ + MC_GOTO(tag##__end) \ + MC_LABEL(tag##__body) + +/* @ALLOWELSE(tag) stmt_0 [else stmt_1]@ * @GOELSE(tag);@ * - * Execute the @before@ statement, followed by @stmt_0@. If @stmt_0@ - * completes, or invokes @break@ or @continue@, then control continues with - * the next statement. If @GOELSE(tag)@ is invoked anywhere in the - * function, then @before@ is executed, followed by @stmt_1@ (if present). - * If @stmt_1@ invokes @break@ then control passes to @onbreak@; if @stmt_1@ - * ends normally then control passes to @onend@. Any @break@ and @continue@ - * in the @before@, @onend@, and @onbreak@ statements behave as one would - * expect from their context. + * Executing @ALLOWELSE@ executes @stmt_0@, but not @stmt_1@. If + * @GOELSE(tag)@ is executed, then control continues from @stmt_1@. */ -#define ALLOWELSE(tag, before, onend, onbreak) \ - if (1) goto MCTRL__LABEL(tag##__allowelse_body); \ - else if (1) MCTRL__LABEL(tag##__allowelse_body_end): ; \ - else if (1) { MCTRL__LABEL(tag##__allowelse_else_end): onend } \ - else if (1) { MCTRL__LABEL(tag##__allowelse_else_break): onbreak } \ - else if (1) { \ - MCTRL__LABEL(tag##__allowelse_before_else): \ - before goto MCTRL__LABEL(tag##__allowelse_else); \ - } else for (;;) \ - if (1) goto MCTRL__LABEL(tag##__allowelse_else_break); \ - else for (;;) \ - if (1) goto MCTRL__LABEL(tag##__allowelse_else_end); \ - else MCTRL__LABEL(tag##__allowelse_else): if (0) for (;;) \ - if (1) goto MCTRL__LABEL(tag##__allowelse_body_end); \ - else MCTRL__LABEL(tag##__allowelse_body): -#define GOELSE(tag) \ - goto MCTRL__LABEL(tag##__allowelse_before_else) +#define ALLOWELSE(tag) \ + MC_GOTO(tag##__body) \ + MC_LABEL(tag##__else) if (0) \ + MC_LABEL(tag##__body) +#define GOELSE(tag) do MC_GOTO(tag##__else); while (0) /* @DOWHILE(tag, cond) stmt@ * @@ -138,8 +139,9 @@ * as one would expect. */ #define DOWHILE(tag, cond) \ - if (1) goto MCTRL__LABEL(tag##__dowhile_body); \ - else while (cond) MCTRL__LABEL(tag##__dowhile_body): + MC_GOTO(tag##__body) \ + while (cond) \ + MC_LABEL(tag##__body) /* @DECL(tag, decl) stmt@ * @@ -150,12 +152,12 @@ */ #if __STDC_VERSION__ >= 199901 || defined(__cplusplus) # define DECL(tag, decl) \ - for (decl;;) \ - if (1) goto MCTRL__LABEL(tag##__decl_body); \ - else if (1) MCTRL__LABEL(tag##__decl_exit): break; \ - else for (;;) \ - if (1) goto MCTRL__LABEL(tag##__decl_exit); \ - else MCTRL__LABEL(tag##__decl_body): + for (decl;;) \ + MC_GOTO(tag##__body) \ + MC_LABEL(tag##__end) MC_ACT({ break; }) \ + for (;;) \ + MC_GOTO(tag##__end) \ + MC_LABEL(tag##__body) #endif /*----- That's all, folks -------------------------------------------------*/ diff --git a/utils/t/control-test.c b/utils/t/control-test.c index 51a9adf..d69572b 100644 --- a/utils/t/control-test.c +++ b/utils/t/control-test.c @@ -53,27 +53,47 @@ static void check_step(int s, const char *where) static void laststep(int s, const char *where) { check_step(s, where); step = 0; } +#define FORELSE(head) \ + MC_GOTO(top) \ + MC_LABEL(out) MC_ACT({ ; }) \ + MC_LABEL(top) ALLOWELSE(els) \ + AFTER(outer, { GOELSE(els); }) \ + for (head) \ + WRAP(inner, { ; }, \ + { ; }, \ + { MC_GOTO(out); }) + #define FOR_FIZZBUZZ(var, base, limit) \ - FIRSTBRANCH(fizzbuzz0) GOBRANCH(fizzbuzz2); \ - MIDBRANCH(fizzbuzz1) ; \ - LASTBRANCH(fizzbuzz2) \ - DECL(fizzbuzz3, int _i = base COMMA _limit = limit) \ - for (; _i < _limit; _i++) \ - DECL(fizzbuzz4, char _buf[24]) \ - DECL(fizzbuzz5, const char *var) \ - WRAP(fizzbuzz6, { \ - switch (_i%15) { \ - case 0: var = "fizzbuzz"; break; \ - case 3: case 6: case 9: case 12: var = "fizz"; break; \ - case 5: case 10: var = "buzz"; break; \ - default: sprintf(_buf, "%d", _i); var = _buf; break; \ - } \ - }, \ - { ; }, \ - { GOBRANCH(fizzbuzz1); }) + MC_GOTO(top) \ + MC_LABEL(out) MC_ACT({ ; }) \ + MC_LABEL(top) DECL(bounds, \ + int _i = base COMMA _limit = limit) \ + for (; _i < _limit; _i++) \ + DECL(buf, char _buf[24]) \ + DECL(var, const char *var) \ + WRAP(wrap, { \ + switch (_i%15) { \ + case 0: \ + var = "fizzbuzz"; \ + break; \ + case 3: case 6: case 9: case 12: \ + var = "fizz"; \ + break; \ + case 5: case 10: \ + var = "buzz"; \ + break; \ + default: \ + sprintf(_buf, "%d", _i); var = _buf; \ + break; \ + } \ + }, \ + { ; }, \ + { MC_GOTO(out); }) int main(void) { + int i; + BEFORE(before0, { STEP(0); }) STEP(1); AFTER(after0, { STEP(3); }) STEP(2); LASTSTEP(4); @@ -89,9 +109,43 @@ int main(void) } LASTSTEP(3); + FORELSE (i = 0; i < 10; i++) { + STEP(i); + if (i == 7) break; + } else + MISSTEP; + LASTSTEP(8); + + FORELSE (i = 0; i < 10; i++) { + STEP(i); + if (i == 12) break; + } else + STEP(10); + LASTSTEP(11); + +#define TEST \ + MC_ACT({ STEP(0); MC_GOTO(in_plain); }) \ + MC_LABEL(done_plain) MC_ACT({ STEP(5); GOELSE(elsie); }) \ + MC_LABEL(in_plain) WRAP(outer_wrap, { STEP(1); }, \ + { STEP(7); }, \ + { MISSTEP; }) \ + ALLOWELSE(elsie) \ + WRAP(inner_wrap, { STEP(2); }, \ + { STEP(4); \ + MC_GOTO(done_plain); }, \ + { MISSTEP; }) \ + STEP(3); \ + else \ + STEP(6); \ + LASTSTEP(8); + TEST +#undef TEST + +#if __STDC_VERSION__ >= 199901 || defined(__cplusplus) STEP(0); - DECL(decl0, int i = 1) STEP(i); + DECL(decl0, int j = 1) STEP(j); LASTSTEP(2); +#endif FOR_FIZZBUZZ(fb, 19, 32) printf("%s\n", fb); -- 2.11.0