.\" -*-nroff-*- .\" .\" Manual for control flow metaprogramming .\" .\" (c) 2023, 2024 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. . .\"-------------------------------------------------------------------------- .so ../defs.man \" @@@PRE@@@ . .\"-------------------------------------------------------------------------- .TH control 3mLib "23 April 2023" "Straylight/Edgeware" "mLib utilities library" .\" @MC_BEFORE .\" @MC_AFTER .\" @MC_WRAP .\" @MC_FINALLY .\" @MC_DOWHILE .\" @MC_DECL .\" @MC_LOOPELSE .\" @MC_LOOPBETWEEN .\" @MC_ALLOWELSE .\" @MC_GOELSE .\" @MC_TARGET .\" @MC_GOTARGET .\" @MC_ACT .\" @MC_LABEL .\" @MC_GOTO . .\"-------------------------------------------------------------------------- .SH NAME control \- control structure metaprogramming . .\"-------------------------------------------------------------------------- .SH SYNOPSIS . .nf .B "#include " .PP .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] .PP .BI MC_TARGET( tag ", " stmt ") " body .BI MC_GOTARGET( tag ); .BI MC_ALLOWELSE( tag ") " main_body " \fR[\fBelse " else_body \fR] .BI MC_GOELSE( tag ); .PP .BI MC_ACT( stmt ) .BI MC_LABEL( tag ) .BI MC_GOTO( tag ) .fi . .\"-------------------------------------------------------------------------- .SH DESCRIPTION . The header file .B 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 .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 .IR tag . 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 .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' .B break and .B continue statements within the .I body behave: we say that these statements are .I captured by the macro. A .B break or .B continue 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 .VS if (!x) break; .VE contains a free .B break statement, while .VS .ta 2n for (i = 0; i < n; i++) 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 .B break is executed. Alas, proper handling of .B continue 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 .IP .BI MC_BEFORE( tag ", " stmt ") " body .PP 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 .B for loop. .B MC_BEFORE does not capture free .B break or .B continue statements. .PP Executing .IP .BI MC_AFTER( tag ", " stmt ") " body .PP has .I nearly the same effect as executing .I stmt followed by .IR body . Again, the whole thing is syntactically a single statement. However, .B MC_AFTER captures free .B break and .B continue statements within the .IR body . A free .B break or .B continue abruptly ends execution of the .IR body , immediately transferring control to the .IR stmt . .PP Executing .IP .BI MC_WRAP( tag ", " before ", " onend ", " onbreak ") " body .PP has .I nearly the same effect as executing .IR before , .IR body , and then .IR onend . If the .I body executes a free .B break statement, then control abruptly continues with the .I onbreak statement, and .I onend is not executed. Currently, if the .I body executes a free .B continue statement, then control abruptly continues with the .I onend statement, but this behaviour is a bug and may be fixed in the future. .PP Executing .IP .BI MC_FINALLY( tag ", " cleanup ") " body .PP 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 .BI MC_DOWHILE( tag ", " cond ") " body .PP has exactly the same effect as .BI "do " body " while (" cond ); \fR, the only difference being that the .I body appears in tail position rather than sandwiched in the middle. .PP Executing .BI MC_DECL( tag ", " decl ") " body has the same effect as .BI "{ " decl "; " body " }" \fR, except that free .B break and .B continue statements are captured. Currently, a free .B continue statement will simply abruptly terminate execution of the .IR body , while a free .B break statement abruptly .I restarts executing the .I body without leaving the scope of the .IR decl ; but these behaviours are bugs and may be fixed in the future. .PP 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. .PP Executing .IP .nf .ta 2n .BI MC_LOOPELSE( head ", " tag ") " .I " loop_body" .RB [ else .IR " else_body" ] .fi .PP 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 ")" .BI "for (" decl "; " cond "; " next_expr ")" .BI "MC_DOWHILE(" tag ", " cond ")" .fi .PP The resulting loop executes the same as .IP .nf .ta 2n .I head .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 .B else clause is present, then the .I else_body is executed. A free .B continue statement within the .I loop_body behaves normally. Free .B break and .B continue statements within the .I else_body are not captured. .PP 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 .BI MC_TARGET( tag ", " stmt ") " body .PP has exactly the same effect as simply executing .IR body . Executing .B MC_GOTARGET immediately transfers control to .IR stmt , with control continuing with the following statement, skipping the .IR body . Free .B break or .B continue 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 .B break within the loop body. See the example below, which shows the definition of .BR MC_LOOPELSE . .PP Executing .IP .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 . Executing .IP .BI MC_GOELSE( tag ); .PP transfers control immediately to .I else_body (if present); control then naturally transfers to the following statement as usual. Free .B break or .B continue statements in either of .I main_body or .I else_body are not captured. .PP Note that .B MC_ALLOWELSE works by secretly inserting an .B if statement head before the .IR main_body , so things will likely to wrong if .I main_body is itself an .B if statement: if .I main_body lacks an .B else clause, then an .B else intended to match .B MC_ALLOWELSE 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 .BR else '. .PP Using these tools, it's relatively straightforward to define a macro like .BR MC_LOOPELSE , described above: .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 .IR action . An action is a valid .IR "statement head" , like .B if or .BR while : i.e., it must be completed by following it with a .I body statement. Executing .IP .BI MC_ACT( stmt ") " body .PP has the same effect as simply executing .IR stmt ; the .I body is usually ignored. Note that .B ; is a valid statement which does nothing, so .BI MC_ACT( stmt ); is also a valid statement with the same effect as .IR stmt . The only way to cause .I body to be executed is to attach a label to it and transfer control using .BR goto . .PP Executing .IP .BI MC_LABEL( tag ") " body .PP has the same effect as .IR body . Executing .IP .BI MC_GOTO( tag ) .PP 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 . The .IR tag s here are scoped to the top-level source line, like all .IR tag s in this macro package. .PP All of the control-flow macros in this package are mainly constructed from .BR MC_ACT , .BR MC_LABEL , and .BR MC_GOTO , sometimes with one or two other statement heads thrown into the mix. For example, .B MC_AFTER is defined as .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 .IB tag __body \fR. Control enters at the start, and is immediately transferred to the .I body ; but the .I body is enclosed in a .B for loop, so when the .I body completes, the loop restarts, transferring control to .IB tag __end and the .IR stmt . Since it is enclosed in .BR MC_ACT , once .I stmt 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 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. . .\"-------------------------------------------------------------------------- .SH "SEE ALSO" . .BR macros (3). .BR mLib (3), .PP Simon Tatham, .IR "Metaprogramming custom control structures in C", .BR "https://www.chiark.greenend.org.uk/~sgtatham/mp/" . . .\"-------------------------------------------------------------------------- .SH "AUTHOR" . Mark Wooding, . .\"----- That's all, folks --------------------------------------------------