chiark / gitweb /
@@@ major overhaul, new primitives mdw/control
authorMark Wooding <mdw@distorted.org.uk>
Tue, 25 Apr 2023 00:21:11 +0000 (01:21 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Tue, 25 Apr 2023 00:21:11 +0000 (01:21 +0100)
utils/control.h
utils/t/control-test.c

index fdc712584f53a27f82bcb45ea274ce522f5fb3c9..77d985a3d8f2f5dec0eef86b43a1a064a36323c5 100644 (file)
  */
 #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.
+/* @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@
  *
  * 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@
  *
  * 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@
  *
  * 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@
  *
  */
 #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 -------------------------------------------------*/
index 51a9adfebc4d03cece5ab0adf8d967a238512fa4..d69572bc91e3498b0a056a0a376e20ec8c763156 100644 (file)
@@ -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);