chiark / gitweb /
@@@ control wip@cb
authorMark Wooding <mdw@distorted.org.uk>
Sat, 22 Apr 2023 20:53:17 +0000 (21:53 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sun, 23 Apr 2023 22:58:36 +0000 (23:58 +0100)
utils/Makefile.am
utils/control.h [new file with mode: 0644]
utils/macros.h
utils/t/control-test.c [new file with mode: 0644]
utils/tests.at

index 2f6ed7cc889c04d9ac59a8a999586d799ca5326b..70c18f0ec45257523222b5ac666a5bd4d8ffa90d 100644 (file)
@@ -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 (file)
index 0000000..433d57b
--- /dev/null
@@ -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 <stmt_0> 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
index 70f535fe51a28ca5a28f28734b7e6d08568dda0c..73230bd23cb524a9459fb4df0f329250886dfa0b 100644 (file)
@@ -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 (file)
index 0000000..f5f4312
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+
+#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 -------------------------------------------------*/
index 3b216fc35962bf24bd021c6f2e492d04e9b3918e..affde41ef96e8d61b241d7bd9d95f79eca8a457c 100644 (file)
@@ -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])