From e6591bec0938b76b8c02083eb6ba101aba4e1b7e Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Sat, 22 Apr 2023 21:53:17 +0100 Subject: [PATCH] @@@ control wip@cb --- utils/Makefile.am | 9 +++ utils/control.h | 165 +++++++++++++++++++++++++++++++++++++++++++++++++ utils/macros.h | 2 + utils/t/control-test.c | 93 ++++++++++++++++++++++++++++ utils/tests.at | 6 ++ 5 files changed, 275 insertions(+) create mode 100644 utils/control.h create mode 100644 utils/t/control-test.c diff --git a/utils/Makefile.am b/utils/Makefile.am index 2f6ed7c..70c18f0 100644 --- a/utils/Makefile.am +++ b/utils/Makefile.am @@ -54,6 +54,15 @@ t_bits_t_CPPFLAGS = $(TEST_CPPFLAGS) t_bits_t_LDFLAGS = -static EXTRA_DIST += t/bits-testgen.py +## Control flow. +pkginclude_HEADERS += control.h +##LIBMANS += control.3 + +check_PROGRAMS += t/control.t +t_control_t_SOURCES = t/control-test.c +t_control_t_CPPFLAGS = $(TEST_CPPFLAGS) +t_control_t_LDFLAGS = -static + ## Exceptions. pkginclude_HEADERS += exc.h libutils_la_SOURCES += exc.c diff --git a/utils/control.h b/utils/control.h new file mode 100644 index 0000000..433d57b --- /dev/null +++ b/utils/control.h @@ -0,0 +1,165 @@ +/* -*-c-*- + * + * Control operators, after Simon Tatham + * + * (c) 2022 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef MLIB_CONTROL_H +#define MLIB_CONTROL_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Header files ------------------------------------------------------*/ + +#ifndef MLIB_MACROS_H +# include "macros.h" +#endif + +/*----- Macros provided ---------------------------------------------------*/ + +/* @MCTRL__LABEL(tag)@ * + * + * Expand to a plausibly unique label based on the current line number and + * the @tag@. + */ +#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. + */ +#define FIRSTBRANCH(tag) if (1) 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) + +/* @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): + +/* @AFTER(tag, stmt_0) stmt_1@ + * + * Execute @stmt_1@ and then @stmt_0@. If either statement invokes @break@ + * then control immediately transfers to the statement following @AFTER@. If + * either invokes @continue@, then control returns to @stmt_0@. + */ +#define AFTER(tag, stmt) \ + if (1) goto MCTRL__LABEL(tag##__after_body); \ + else for (;;) \ + if (1) { stmt break; } \ + else MCTRL__LABEL(tag##__after_body): + +/* @WRAP(tag, before, onend, onbreak) stmt@ + * + * Execute the @before@ statement, followed by @stmt@. If @stmt@ invokes + * @break@, then @onbreak@ is immediately executed; if @stmt@ completes + * normally, or invokes @continue@ then @onend@ is immediately executed. + * Any @break@ and @continue@ in the @before@, @onend@, and @onbreak@ + * 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]@ + * @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. + */ +#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) + +/* @DOWHILE(tag, cond) stmt@ + * + * Repeatedly execute @stmt@ until @cond@ evaluates to zero. Execute @stmt@ + * at least once. The @break@ and @continue@ statements work within @stmt@ + * as one would expect. + */ +#define DOWHILE(tag, cond) \ + if (1) goto MCTRL__LABEL(tag##__dowhile_body); \ + else while (cond) MCTRL__LABEL(tag##__dowhile_body): + +/* @DECL(tag, decl) stmt@ + * + * Execute @stmt@ with @decl@ in scope. If @stmt@ completes or invokes + * @break@ or @continue@ then control continues with the statement following + * @DECL@. Internally, this uses @for@, so it only works in C99 or later, or + * C++. + */ +#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): +#endif + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/utils/macros.h b/utils/macros.h index 70f535f..73230bd 100644 --- a/utils/macros.h +++ b/utils/macros.h @@ -57,6 +57,8 @@ IGNORABLE extern char static_assert_failed[2*!!(cond) - 1] #endif +#define COMMA , + /*----- String and character hacks ----------------------------------------*/ #define CTYPE_HACK(func, ch) (func((unsigned char)(ch))) diff --git a/utils/t/control-test.c b/utils/t/control-test.c new file mode 100644 index 0000000..f5f4312 --- /dev/null +++ b/utils/t/control-test.c @@ -0,0 +1,93 @@ +/* -*-c-*- + * + * Test the control-flow metaprogramming macros + * + * (c) 2022 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include + +#include "control.h" + +/*----- Main code ---------------------------------------------------------*/ + +static int step = 0; +static int rc = 0; + +#define STEP(s) check_step(s, __FILE__ ": " STR(__LINE__)) +#define MISSTEP STEP(-1) +static void check_step(int s, const char *where) +{ + if (step != s) { + fprintf(stderr, "misstep at %s: expected %d but found %d\n", + where, step, s); + rc = 2; + } + step++; +} + +#define LASTSTEP(s) laststep(s, __FILE__ ": " STR(__LINE__)) +static void laststep(int s, const char *where) + { check_step(s, where); step = 0; } + +#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); }) + +int main(void) +{ + BEFORE(before0, { STEP(0); };) STEP(1); + AFTER(after0, { STEP(3); };) STEP(2); + LASTSTEP(4); + + WRAP(wrap0, { STEP(0); }, { STEP(2); }, { MISSTEP; }) STEP(1); + WRAP(wrap1, { STEP(3); }, { MISSTEP; }, { STEP(5); }) { STEP(4); break; } + LASTSTEP(6); + + STEP(0); + DECL(decl0, int i = 1) STEP(i); + LASTSTEP(2); + + FOR_FIZZBUZZ(fb, 19, 27) printf("%s\n", fb); + + return (rc); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/utils/tests.at b/utils/tests.at index 3b216fc..affde41 100644 --- a/utils/tests.at +++ b/utils/tests.at @@ -36,6 +36,12 @@ for seed in 0xaca98e08 0x0b6e95fb ""; do done AT_CLEANUP +## control +AT_SETUP([utilities: control]) +AT_KEYWORDS([utils control]) +AT_CHECK([BUILDDIR/t/control.t], [0]) +AT_CLEANUP + ## exc AT_SETUP([utilities: exc]) AT_KEYWORDS([utils exc]) -- 2.11.0