@@@ man wip
[mLib] / utils / control.3
index 79e7df9..be83be9 100644 (file)
@@ -1,5 +1,18 @@
 .\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
 .TH control 3 "23 April 2023" "Straylight/Edgeware" "mLib utilities library"
+.
 .SH NAME
 control \- control structure metaprogramming
 .\" @MC_BEFORE
@@ -17,6 +30,7 @@ control \- control structure metaprogramming
 .\" @MC_ACT
 .\" @MC_LABEL
 .\" @MC_GOTO
+.
 .SH SYNOPSIS
 .nf
 .B "#include <mLib/control.h>"
@@ -24,9 +38,11 @@ control \- control structure metaprogramming
 .BI MC_BEFORE( tag ", " stmts ") " body
 .BI MC_AFTER( tag ", " stmts ") " body
 .BI MC_WRAP( tag ", " before_stmt ", " onend_stmt ", " onbreak_stmt ") " body
+.BI MC_FINALLY( tag ", " cleanup ") " body
 .BI MC_DOWHILE( tag ", " cond ") " body
 .BI MC_DECL( tag ", " decl ") " body
 .BI MC_LOOPELSE( tag ", " head ") " loop_body " \fR[\fBelse " else_body \fR]
+.BI MC_LOOPBETWEEN( tag ", " setup ", " cond ", " step ") " loop_body " \fR[\fBelse " else_body \fR]
 
 .BI MC_TARGET( tag ", " stmt ") " body
 .BI MC_GOTARGET( tag );
@@ -37,80 +53,95 @@ control \- control structure metaprogramming
 .BI MC_LABEL( tag )
 .BI MC_GOTO( tag )
 .fi
+.
 .SH DESCRIPTION
 The header file
 .B <mLib/control.h>
-defines a number of macros which are useful when defining new
-control structures for C.  They are inspired by Simon Tatham's article
+defines a number of macros which are useful
+when defining new control structures for C.
+They are inspired by Simon Tatham's article
 .IR "Metaprogramming custom control structures in C",
 though these macros differ from Tatham's in a few respects.
+.
 .SS "Common features"
 Each of these macros takes a
 .I tag
-argument.  A
+argument.
+A
 .I tag
-is lexically like an identifier, except that it may begin with a digit,
-so, for example, plain integers are acceptable tags.  Each use of an
-action macro by a user-level macro must have a distinct
+is lexically like an identifier,
+except that it may begin with a digit,
+so, for example, plain integers are acceptable tags.
+Each use of an action macro by a user-level macro
+must have a distinct
 .IR tag .
-If you're writing a new prefix action macro written in terms of these
-existing actions, your macro should receive a
+If you're writing a new prefix action macro
+written in terms of these existing actions,
+your macro should receive a
 .I tag
-from its caller, and pass this tag, along with a distinctive component
-of its own, down to any prefix actions that it calls; the
+from its caller,
+and pass this tag,
+along with a distinctive component of its own,
+down to any prefix actions that it calls;
+the
 .IR tag s
 from each layer should be separated by a pair of underscores.
 .PP
 Some of these macros work by wrapping a loop around the
 .I body
-statement.  This interferes with the way that `free'
+statement.
+This interferes with the way that `free'
 .B break
 and
 .B continue
 statements within the
 .I body
-behave: we say that these statements are
+behave:
+we say that these statements are
 .I captured
 by the macro.
 A
 .B break
 or
 .B continue
-statement is `free' if it doesn't appear lexically within a loop or
+statement is
+.I free
+if it doesn't appear lexically within a loop or
 (for
 .B break)
 .B switch
 statement that is part of the
 .IR body .
 So
-.IP
-.B "if (!x) break;"
-.PP
+.VS
+if (!x) break;
+.VE
 contains a free
 .B break
-statement, while
-.IP
-.nf
-.ft B
+statement,
+while
+.VS
+.ta 2n
 for (i = 0; i < n; i++)
-\h'4n'if (interestingp(i)) break;
-.ft
-.fi
-.PP
+       if (interestingp(i)) break;
+.VE
 does not.
 .PP
-Some of these macros take special care to give you control over what
-happens when a captured
+Some of these macros take special care
+to give you control over what happens when a captured
 .B break
-is executed.  Alas, proper handling of
+is executed.
+Alas, proper handling of
 .B continue
-doesn't seem possible.  Free
+doesn't seem possible.
+Free
 .B break
 and
 .B continue
 statements
 .I within
 arguments to these macros are never captured.
+.
 .SS "Prefix action macros"
 .B MC_BEFORE
 macro is the simplest to understand.  Executing
@@ -121,8 +152,9 @@ has the same effect as executing
 .I stmt
 followed by
 .IR body ,
-except that the whole thing is syntactically a single statement, so, for
-example, it doesn't need to be enclosed in braces to be the body of a
+except that the whole thing is syntactically a single statement,
+so, for example, it doesn't need to be enclosed in braces
+to be the body of a
 .B for
 loop.
 .B MC_BEFORE
@@ -142,7 +174,8 @@ the same effect as executing
 .I stmt
 followed by
 .IR body .
-Again, the whole thing is syntactically a single statement.  However,
+Again, the whole thing is syntactically a single statement.
+However,
 .B MC_AFTER
 captures free
 .B break
@@ -178,15 +211,46 @@ statement, then control abruptly continues with the
 .I onbreak
 statement, and
 .I onend
-is not executed.  Currently, if the
+is not executed.
+Currently, if the
 .I body
 executes a free
 .B continue
-statement, then control abruptly continues with the
+statement,
+then control abruptly continues with the
 .I onend
-statement, but this behaviour is a bug and may be fixed in the future.
+statement,
+but this behaviour is a bug and may be fixed in the future.
+.PP
+Executing
+.IP
+.BI MC_FINALLY( tag ", " cleanup ") " body
 .PP
-.\" @@@ mc_finally
+has the same effect as executing
+.I body
+followed by
+.IR cleanup ,
+except that a free
+.B break
+statement within
+.I body
+will execute
+.I cleanup
+before propagating the
+.B break
+to the enclosing context.
+A free
+.B continue
+statement currently causes control to continue abruptly with
+.I cleanup
+but this behaviour is a bug and may be fixed in the future.
+The
+.I cleanup
+code is textually duplicated,
+so there'll be some code bloat if this is very complex.
+If it arranges to have private long-term state
+then the two copies will not share this state,
+so probably don't do this.
 .PP
 Executing
 .IP
@@ -206,7 +270,8 @@ except that free
 .B break
 and
 .B continue
-statements are captured.  Currently, a free
+statements are captured.
+Currently, a free
 .B continue
 statement will simply abruptly terminate execution of the
 .IR body ,
@@ -224,47 +289,56 @@ The
 .B MC_DECL
 macro makes use of the fact that a
 .B for
-statement can introduce a declaration into its body's scope in C99 and
-C++; the macro is not available in C89.
+statement can introduce a declaration
+into its body's scope in C99 and C++;
+the macro is not available in C89.
 .PP
 Executing
 .IP
 .nf
+.ta 2n
 .BI MC_LOOPELSE( head ", " tag ") "
-.RI \h'4n' loop_body
+.I "   loop_body"
 .RB [ else
-.RI \h'4n' else_body ]
+.IR "  else_body" ]
 .fi
 .PP
-results in Python-like loop behaviour.  The
+results in Python-like loop behaviour.
+The
 .I head
 must be a valid loop head with one of the forms
 .IP
+.nf
 .BI "while (" cond ")"
-.br
 .BI "for (" decl "; " cond "; " next_expr ")"
-.br
 .BI "MC_DOWHILE(" tag ", " cond ")"
+.fi
 .PP
 The resulting loop executes the same as
 .IP
 .nf
+.ta 2n
 .I head
-.RI \h'4n' loop_body
+.I "   loop_body"
 .fi
 .PP
 If the loop ends abruptly, as a result of
 .BR break ,
-then control is passed to the statement following the loop in the usual
-way.  However, if the loop completes naturally, and the optional
+then control is passed to the statement following the loop
+in the usual way.
+However, if the loop completes naturally,
+and the optional
 .B else
-clause is present, then the
+clause is present,
+then the
 .I else_body
-is executed.  A free
+is executed.
+A free
 .B continue
 statement within the
 .I loop_body
-behaves normally.  Free
+behaves normally.
+Free
 .B break
 and
 .B continue
@@ -272,7 +346,63 @@ statements within the
 .I else_body
 are not captured.
 .PP
-.\" @@@ loopbetween
+Executing
+.IP
+.nf
+.ta 2n
+.BI MC_LOOPBETWEEN( tag ", " setup ", " cond ", " step ") "
+.I "   loop-body"
+.RB [ else
+.IR "  else-body" ]
+.fi
+.PP
+is similar to executing the
+.B for
+loop
+.IP
+.ta 2n
+.nf
+.BI "for (" setup "; " cond "; " step ") "
+.I "   loop-body"
+.fi
+.PP
+except that, once the
+.I loop_body
+has finished,
+the
+.I step
+expression evaluated,
+and the
+.I cond
+evaluated and determined to be nonzero,
+the
+.I else_body
+(if any) is executed before re-entering the
+.IR loop_body .
+This makes it a useful place to insert
+any kind of interstitial material,
+e.g., printing commas between list items.
+Note that by the time the
+.I else_body
+is executed,
+the decision has already been made
+that another iteration will be performed,
+and, in particular, the
+.I step
+has occurred.  The
+.I else_body
+is therefore looking at the next item to be processed,
+not the item that has just finished being processed.
+The
+.I cond
+is textually duplicated,
+so there'll be some code bloat if this is very complex.
+If it somehow manages to have private long-term state
+(e.g., as a result of declaring static variables
+inside GCC statement expressions)
+then the two copies will not share this state,
+so probably don't do this.
+.
 .SS "Lower-level machinery"
 Executing
 .IP
@@ -284,7 +414,8 @@ Executing
 .B MC_GOTARGET
 immediately transfers control to
 .IR stmt ,
-with control continuing with the following statement, skipping the
+with control continuing with the following statement,
+skipping the
 .IR body .
 Free
 .B break
@@ -294,16 +425,24 @@ statements in
 .I body
 are not captured.
 .PP
-This is most commonly useful in loops in order to arrange the correct
-behaviour of a free
+This is most commonly useful in loops
+in order to arrange the correct behaviour of a free
 .B break
-within the loop body.  See the example below, which shows the definition
+within the loop body.
+See the example below,
+which shows the definition
 of
 .BR MC_LOOPELSE .
 .PP
 Executing
 .IP
-.BI MC_ALLOWELSE( tag ") " main_body " \fR[\fBelse " else_body \fR]
+.nf
+.ta 2n
+.BI MC_ALLOWELSE( tag ") "
+.I "   main_body"
+.RB [ else
+.IR "  else_body" ]
+.fi
 .PP
 has exactly the same effect as just
 .IR main_body .
@@ -313,8 +452,9 @@ Executing
 .PP
 transfers control immediately to
 .I else_body
-(if present); control then naturally transfers to the following
-statement as usual.  Free
+(if present);
+control then naturally transfers to the following statement as usual.
+Free
 .B break
 or
 .B continue
@@ -334,40 +474,41 @@ so things will likely to wrong if
 .I main_body
 is itself an
 .B if
-statement: if
+statement:
+if
 .I main_body
 lacks an
 .B else
-clause, then an
+clause,
+then an
 .B else
 intended to match
 .B MC_ALLOWELSE
-will be mis-associated; and even if
+will be mis-associated;
+and even if
 .I main_body
 .I does
 have an
 .B else
-clause, the resulting program text is likely to provoke a compiler
-warning about `dangling
+clause,
+the resulting program text is likely to provoke a compiler warning
+about `dangling
 .BR else '.
 .PP
-Using these tools, it's relatively straightforward to define a macro
-like
+Using these tools,
+it's relatively straightforward to define a macro like
 .BR MC_LOOPELSE ,
 described above:
-.IP
-.nf
-.ft B
-#define MC_LOOPELSE(tag, head) \e
-\h'4n'MC_TARGET(tag##__exit, { ; }) \e
-\h'4n'MC_ALLOWELSE(tag##__else) \e
-\h'4n'MC_AFTER(tag##__after, { MC_GOELSE(tag##__else); }) \e
-\h'4n'head \e
-\h'8n'MC_WRAP(tag##__body, { ; }, { ; }, \e
-\h'8n+\w'MC_WRAP(tag##__body, ''{ MC_GOTARGET(tag##__exit); })
-.ft R
-.fi
-.PP
+.VS
+.ta 4n 4n+\w'\fBMC_WRAP(tag##__body, 'u \n(.lu-\n(.iu-4n
+#define MC_LOOPELSE(tag, head)                 \e
+       MC_TARGET(tag##__exit, { ; })           \e
+       MC_ALLOWELSE(tag##__else)               \e
+       MC_AFTER(tag##__after, { MC_GOELSE(tag##__else); })             \e
+       head            \e
+       MC_WRAP(tag##__body, { ; }, { ; },              \e
+               { MC_GOTARGET(tag##__exit); })
+.VE
 The main `trick' for these control-flow macros is
 .BR MC_ACT ,
 which wraps up a statement as an
@@ -380,7 +521,8 @@ or
 .BR while :
 i.e., it must be completed by following it with a
 .I body
-statement.  Executing
+statement.
+Executing
 .IP
 .BI MC_ACT( stmt ") " body
 .PP
@@ -388,9 +530,11 @@ has the same effect as simply executing
 .IR stmt ;
 the
 .I body
-is usually ignored.  Note that
+is usually ignored.
+Note that
 .B ;
-is a valid statement which does nothing, so
+is a valid statement which does nothing,
+so
 .BI MC_ACT( stmt );
 is also a valid statement with the same effect as
 .IR stmt .
@@ -413,12 +557,13 @@ immediately transfers control to the
 .IR body .
 Note that
 .B MC_GOTO
-is syntactically an action
-(i.e., it's wrapped in
-.BR MC_ACT ).
+is syntactically an action,
+i.e., it's wrapped in
+.BR MC_ACT .
 The
 .IR tag s
-here are scoped to the top-level source line, like all
+here are scoped to the top-level source line,
+like all
 .IR tag s
 in this macro package.
 .PP
@@ -431,26 +576,26 @@ sometimes with one or two other statement heads thrown into the mix.
 For example,
 .B MC_AFTER
 is defined as
-.IP
-.nf
-.ft B
-#define MC_AFTER(tag, stmt) \e
-\h'28n'MC_GOTO(tag##__body) \e
-\h'4n'MC_LABEL(tag##__end) \e
-\h'28n'MC_ACT(stmt) \e
-\h'28n'for (;;) \e
-\h'32n'MC_GOTO(tag##__end) \e
-\h'4n'MC_LABEL(tag##__body)
-.ft R
-.fi
-.PP
-(The unusual layout is conventional, to make the overall structure of
-the code clear despite visual interference from the labels.)
+.VS
+.ta 4n 28n 30n \n(.lu-\n(.iu-4n
+#define MC_AFTER(tag, stmt)                    \e
+               MC_GOTO(tag##__body)            \e
+       MC_LABEL(tag##__end)                    \e
+               MC_ACT(stmt)            \e
+               for (;;)                \e
+                       MC_GOTO(tag##__end)     \e
+       MC_LABEL(tag##__body)
+.VE
+(The unusual layout is conventional,
+to make the overall structure of the code clear
+despite visual interference from the labels.)
 The
 .I body
-appears at the end, labelled as
+appears at the end,
+labelled as
 .IB tag __body \fR.
-Control enters at the start, and is immediately transferred to the
+Control enters at the start,
+and is immediately transferred to the
 .I body ;
 but the
 .I body
@@ -458,7 +603,8 @@ is enclosed in a
 .B for
 loop, so when the
 .I body
-completes, the loop restarts, transferring control to
+completes, the loop restarts,
+transferring control to
 .IB tag __end
 and the
 .IR stmt .
@@ -466,21 +612,33 @@ Since it is enclosed in
 .BR MC_ACT ,
 once
 .I stmt
-completes, control transfers to the following statement.
-.SH BUGS
+completes,
+control transfers to the following statement.
+.
+.SH "BUGS"
 Some macros cause free
 .B break
 and/or
 .B continue
 statements to behave in unexpected ways.
 .PP
-The need for tagging is ugly, and the restriction on having two
-user-facing control-flow macros on the same line is objectionable.  The
-latter could be avoided by using nonstandard features such as GCC's
+It's rather hard to use
+.B MC_ALLOWELSE
+in practice without provoking
+.RB `dangling- else '
+warnings.
+.PP
+The need for tagging is ugly,
+and the restriction on having two
+user-facing control-flow macros on the same line is objectionable.
+The latter could be avoided
+by using nonstandard features such as GCC's
 .B __COUNTER__
-macro, but adopting that would do programmers a disservice by
-introducing a hazard for those trying to port code to other compilers
-which lack any such feature.
+macro,
+but adopting that would do programmers a disservice
+by introducing a hazard for those
+trying to port code to other compilers which lack any such feature.
+.
 .SH "SEE ALSO"
 .BR mLib (3),
 .BR macros (3).
@@ -488,5 +646,6 @@ which lack any such feature.
 Simon Tatham,
 .IR "Metaprogramming custom control structures in C",
 .BR "https://www.chiark.greenend.org.uk/~sgtatham/mp/" .
+.
 .SH "AUTHOR"
 Mark Wooding, <mdw@distorted.org.uk>