.\" -*-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
.\" @MC_ACT
.\" @MC_LABEL
.\" @MC_GOTO
+.
.SH SYNOPSIS
.nf
.B "#include <mLib/control.h>"
.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 );
.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
.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
.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
.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
.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 ,
.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
.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
.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
.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 .
.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
.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
.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
.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 .
.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
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
.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 .
.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).
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>