.\" cppawk: C preprocessor wrapper around awk .\" Copyright 2022 Kaz Kylheku .\" .\" BSD-2 License .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions are met: .\" .\" 1. Redistributions of source code must retain the above copyright notice, .\" this list of conditions and the following disclaimer. .\" .\" 2. Redistributions in binary form must reproduce the above copyright notice, .\" this list of conditions and the following disclaimer in the documentation .\" and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" .\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE .\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR .\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF .\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS .\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN .\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .de bk .IP " " .PP .. .TH CPPAWK-ITER 1 "19 April 2022" "cppawk Libraries" "Iteration" .SH NAME iter \- powerful, user-extensible iteration language for Awk .SH SYNOPSIS .ft B #include \fI// Simple, single-variable iteration\fP doarray (\fIkey\fP, \fIvalue\fP, \fIarr\fP) \fI// iterate over Awk assoc array\fP \fIstatement\fP dostring (\fIidx\fP, \fIchr\fP, \fIstr\fP) \fI// iterate over string\fP \fIstatement\fP dofields (\fIidx\fP, \fIval\fP) \fI// iterate over $1, $2, ..\fP \fIstatement\fP \fI// Multi-clause parallel and nested iteration\fP loop (\fIclause1\fP, \fIclause2\fP, ...) \fI// Parallel iteration\fP \fIstatement\fP loop_nest (\fIclause1\fP, \fI// Nested iteration\fP \fIclause2\fP, parallel (\fIclause3\fP, \fIclause4\fP, ...), ...) \fIstatement\fP \fI// Clauses for loop/loop_iter\fP \fI// numeric stepping clauses\fP range (\fIidx\fP, \fIfrom\fP, \fIto\fP) range_step (\fIidx\fP, \fIfrom\fP, \fIto\fP, \fIstep\fP) from (\fIidx\fP, \fIfrom\fP) from_step (\fIidx\fP, \fIfrom\fP, \fIstep\fP) \fI// general stepping clause\fP first_then (\fIvar\fP, \fIfirst\fP, \fIthen\fP) \fI// container traversal\fP str (\fIidx\fP, \fIch\fP, \fIstr\fP) list (\fIiter\fP, \fIvar\fP, \fIlist\fP) fields (\fIvar\fP) keys (\fIkey\fP, \fIarray\fP) records (\fIfile\fP) \fI// collect items into lists\fP collect (\fIvar\fP, \fIexpr\fP) collect_plus (\fIvar\fP, \fIexpr\fP) \fI// calculating clauses\fP summing (\fIvar\fP, \fIexpr\fP) counting (\fIvar\fP, \fIexpr\fP) maximizing (\fIvar\fP, \fIexpr\fP) minimizing (\fIvar\fP, \fIexpr\fP) argmax (\fImaxvar\fP, \fIarg\fP, \fIexpr\fP) argmin (\fIminvar\fP, \fIarg\fP, \fIexpr\fP) \fI// termination control clauses\fP while (\fIexpr\fP) until (\fIexpr\fP) \fI// parallel grouping combinator\fP \fI// clause1, clause2, ... are parallel even in a loop_nest.\fP parallel (\fIclause1\fP, \fIclause2\fP, ...) \fI// conditional combinator:\fP \fI// dependent clause steps/tests only whenever test is true\fP if (\fItest\fP, \fIclause\fP) .ft R .SH OVERVIEW .bk The .B header provides constructs for expressing iteration. For simple loops that occur often \(em iterating over an array, string or the positional fields \(em several dedicated constructs are provided: .BR doarray , .B dostring and .BR dofields . The separate .B header also provides simple iteration, for lists. In addition, .B provides a powerful general iteration facility which allows multiple variables to be iterated in parallel or nested loops, stepping over various kinds of spaces, with special clauses for calculation, collecting lists, or controlling termination. Furthermore, a new clauses may easily be defined by the application programmer, simply by defining six macros according to easy-to-follow rules. Clauses are susceptible to macro expansion: new clauses can be defined by writing macros that expand to existing clauses. In all of the iteration constructs of .B the variables which are supplied by the application are subject to assignment; the constructs do not bind these variables. It is up to the application to control the scope of these variables. In general, immediately after the termination of these looping constructs, the variables explicitly specified by the application code remain visible, and continue to hold the values they had immediately prior to loop termination. .SH SIMPLE ITERATION .bk .SS Macro \fIdoarray\fP .bk .B Syntax: .ft B doarray (\fIkey\fP, \fIvalue\fP, \fIarr\fP) \fIstatement\fP .ft R .B Description: The .B doarray macro executes the .I statement for every element of the associative array .IR arr . Prior to each iteration, the variables .I key and .I value are set to the next key and value to be visited. Awk associative arrays are not required to maintain order; thus .B doarray does not traverse .I arr in any required order. .B Example: .ft B \fI// assuming a is prepared like this:\fP split("a:b:c", \fIa\fP, /:/) \fI// possible output is\fP \fI// 3 c\fP \fI// 1 a\fP \fI// 2 b\fP doarray (\fIk\fP, \fIv\fP, \fIa\fP) print \fIk\fP, \fIv\fP .ft R .SS Macro \fIdostring\fP .bk .B Syntax: .ft B dostring (\fIidx\fP, \fIchr\fP, \fIstr\fP) \fIstatement\fP .ft R .B Description: The .B dostring macro evaluates .I statement for all successive substrings of length 1 of string .IR str . The variable .I idx steps from 1 up to the length of .IR str , and the .I chr variable takes on string values of length 1. On the first iteration, .I chr contains the first character of .IR str , then on the second iteration the second character and so forth. The .I str expression is evaluated only once. .B Example: .ft B \fI// output is:\fP \fI// 1 a\fP \fI// 2 b\fP \fI// 3 c\fP dostring (\fIi\fP, \fIch\fP, "abc") print \fIi\fP, \fIch\fP .ft R .SS Macro \fIdofields\fP .bk .B Syntax: .ft B dofields (\fIidx\fP, \fIval\fP) \fIstatement\fP .ft R .B Description: The .B dofields macro iterates over the Awk positional fields, executing .I statement for each iteration. The .I idx variable is initialized to 1. Before every iteration, .I idx is compared compared to the current value of .BR NR . The iteration proceeds if .I i .B <= .B NR is true. After the execution of .IR statement , .I idx is incremented by one. Before each iteration, .I val is set to the value of the positional field indicated by .IR idx , namely \fB$\fP\fIidx\fP. .B Example: .ft B \fI// set fields, assuming default FS\fP $0 = "the quick brown fox" \fI// output is:\fP \fI// 1 the\fP \fI// 2 quick\fP \fI// 3 brown\fP \fI// 4 fox\fP dofields (i, v) print \fIi\fP, \fIv\fP .ft P .SH THE LOOP MACRO .bk .SH Macro \fIloop\fP .bk .B Syntax: .ft B loop (\fIclause1\fP, \fIclause2\fP, ...) \fIstatement\fP .ft R .B Description: The .B loop macro repeatedly executes .I statement under the control of one or more clauses: .IR clause1 , .iR clause2 , ... Each clause contributes to the initial loop conditions, termination testing, and actions of the loop. Under .B loop the clauses act in parallel. The same clauses may be combined into a nested loop using the .B loop_nest macro. The term .I parallel here doesn't refer to concurrent processing with threads or processors, but to the lock-step performance of loop iteration steps. Each clause communicates to .B loop the following: .IP initialization What variable are to be be prepared with what initial values. .IP termination What conditions will terminate the loop. Prior to each iteration, the termination test from every clause is interrogated. The loop .I statement executes only if all clauses indicate continued execution. If at least one clause calls for termination, the loop ends. .IP preparation Whenever all clauses indicate that the loop continues, each clause has the opportunity to make some preparation prior to the execution of the loop, such as calculating the values of some variables. .IP stepping After the execution of each statement, each clause has the opportunity to perform some increment step. .IP finalization When the loop terminates, some clauses execute some special code to bring about a needed final state in their variables, or for some other reason. .PP The Awk .B break and .B continue statements are usable inside .B loop and behave like they do inside the .B for construct. The .B break statement terminates the loop, and .B continue terminates the .I statement only, proceeding to the increment step which prepares for the next iteration. .B Example: .ft B \fI// step variable i from 1 to 5 by 1,\fB \fI// and variable j from 100 to 500 in steps of 100.\fB \fI// output is:\fB \fI// 1 100\fB \fI// 2 200\fB \fI// 3 300\fB \fI// 4 400\fB \fI// 5 500\fB loop (range (\fIi\fB, 1, 5), range_step (\fIj\fB, 100, 500, 100)) { print \fIi\fB, \fIj\fB } .ft R More example are given in the documentation of the clauses. .SH Macro \fIloop_nest\fP .bk .B Syntax: .ft B loop_nest (\fIclause1\fP, \fIclause2\fP, ...) \fIstatement\fP .ft R .B Description: The .B loop_nest macro has a syntax resembling that of .BR loop . Unlike .BR loop , it generates a nested loop: the logic of the clauses is arranged into loop nestings. Each clause controls its own loop, in which the loops of subsequent clauses are nested. In effect, the .B loop_nest syntax is a shorthand for writing: .ft B loop (\fIclause1\fP) loop (\fIclause2\fP) loop (\fIclause3\fP) ... loop (\fIclauseN\fP) \fIstatement\fP .ft R There is a special clause called .B parallel which is useful inside a .BR loop_nest . Detailed documentation for it is given in its own section. The .B parallel clause combines multiple clauses into a single clause, in such a way that those clauses are executed in parallel regardless of which loop macro is used. Therefore the following equivalence also holds: Note: consistently with the semantics of .B loop_nest being that of the above shorthand, the .B break and .B continue statements affect only the innermost loop corresponding to the last clause, .IR clauseN . The .B break statement only breaks out of that loop, and .B continue only skips to the iteration part of that loop. Note: the semantics of all clauses such as termination control clauses and list collection clauses must also be understood in terms of the nesting. For instance if a collect clause is nested inside another loop which repeats three times, then that collection will be repeated three times: the collection variable will be initialized three times, collection will be performed three times. Only the items collected by the last of the three repetitions of the collect loop will be retained. Or, if instead of collect, maximize is used to calculate a maximum value, then three maxima will be calculated over the three invocations of the maximizing loop, only the effect of the last of which will be retained in the variable which receives the maximum value. .ft B loop (\fIclause1\fP, \fIclause2\fP, ..., \fIclause\fP) \fIstatement\fP .ft R may be achieved using .ft B loop_nest (parallel (\fIclause1\fP, \fIclause2\fP, ..., \fIclauseN\fP)) \fIstatement\fP .ft R .B Example: .ft B #include #include BEGIN { loop_nest (list(it, let, list("a", "b", "c")), range(x, 1, 3)) print let "-" x } Output: a-1 a-2 a-3 b-1 b-2 b-3 c-1 c-2 c-3 .ft R .SH LOOP CLAUSES: NUMERIC AND GENERAL STEPPING .bk .SS Loop clauses \fIrange\fP and \fIrange_step\fP .bk .B Syntax: .ft B range (\fIidx\fP, \fIfrom\fP, \fIto\fP) range_step (\fIidx\fP, \fIfrom\fP, \fIto\fP, \fIstep\fP) .ft R .B Description: The .B range loop clause initializes the .I idx variable to the value of the .I from expression. Prior to each loop iteration, the expression .BI idx " <= (to) is tested. If it is false, the loop terminates. After each execution of .IR statement , .I idx is incremented by 1. The .I to expression is reevaluated at the beginning of each iteration, so its value may change. The .B range_step clause is a variation of .B range which allows the amount added to .I idx to be specified as the .I step expression. The .I step expression is evaluated after each iteration, so its value may change. That value is added to .I idx . Note: .B loop clauses may not have optional arguments; it is not possible to write a single loop clause which takes an optional step size that defaults to 1. .SS Loop clauses \fIfrom\fP and \fIfrom_step\fP .bk .B Syntax: .ft B from (\fIidx\fP, \fIfrom\fP) from_step (\fIidx\fP, \fIfrom\fP, \fIstep\fP) .ft R .B Description: The .B from clause is similar to .BI range , except that the .I to expression is missing. The clause performs no termination test; it initializes .I idx to the .I from value and then executes indefinitely, forever incrementing .B idx by one. In order for the loop to terminate, another clause must be present which requests termination, or else .B break must be used to terminate the loop abruptly. The .B from_step clause is a variant of .B from which allows the amount added to .I idx at the increment step to be determined by the value of the .I step expression, which is reevaluated each time. .SH LOOP CLAUSES: CONTAINER TRAVERSAL .bk .SS Loop clause \fIstr\fP .bk .B Syntax: .ft B str (\fIidx\fP, \fIch\fP, \fIstr\fP) .ft P .B Description: The .B str loop clause iterates over a string. The .I str expression is evaluated once to produce a string. The .I idx variable steps from 1 to up to the length of the string. If the string is empty, the loop terminates without any iterations taking place. Prior to each iteration, the .I ch variable is set to a one-character-long substring of the string starting at the .I idx position. .SS Loop clause \fIlist\fP .bk .B Syntax: .ft B list (\fIiter\fP, \fIvar\fP, \fIlist\fP) .ft R .B Description: The .B list loop clause iterates over the elements of a list. Note: the inclusion of the .B header does not make visible list manipulation libraries such as .BR , The .I iter variable is initialized to .IR list . Prior to each iteration, .I iter is tested for termination as if using the .B endp function. If .I iter refers to a nonempty list, and thus iteration may continue, then .I var is set to the first item in .IR iter , .BI car( iter ) \fR,\fP prior to the execution of the loop .IR statement . After each iteration, .B iter is replaced with .BI cdr( iter ) \fR.\fP .SS Loop clause \fIfields\fP .bk .B Syntax: .ft B fields (\fIvar\fP) .ft R .B Description: The .B fields loop clause iterates over the Awk positional fields. An internal counter is initialized to 1. Iteration proceeds if this counter is less than or equal to the current value of .BR NF . The counter is incremented by 1 after each iteration. Prior to the execution of the loop .IR statement , .I var is set to the field indicated by the internal counter. .SS Loop clause \fIkeys\fP .bk .B Syntax: .ft B keys (\fIkey\fP, \fIarray\fP) .ft R .B Description: The .B keys loop clause iterates over the keys (indices) of an Awk associative array named by the .I array parameter. The .I key variable is set to each index in turn. The keys are not visited in any specific, required order. .SS Loop clause \fIrecords\fP .bk .B Syntax: .ft B records (\fIfile\fP) .ft R .B Description: The .B records clause is based on the Awk .B getline operator. The .I file expression is evaluated once, prior to the loop, and its value is remembered. Then prior to each iteration of the loop, a record is read from the file using .B getline which has the effect of setting the record parameter .B $0 and the positional parameters .BR $1 , .BR $2 ", ..." in the manner documented of the .B getline operator. When the loop terminates normally, the file is closed, unless it is the string .B \(dq-\(dq denoting standard input. .SH LOOP CLAUSES: COLLECTION INTO LISTS .bk .SS Loop clauses \fIcollect\fP and \fIcollect_plus\fP .bk .B Syntax: .ft B collect (\fIvar\fP, \fIexpr\fP) collect_plus (\fIvar\fP, \fIexpr\fP) .ft R .B Description: The .B collect clause initializes .I var to an empty bag object as if by using the .B list_init macro from .BR . The clause provides no termination test; if the only clauses in a .B loop are .B collect clauses, then it will not terminate. Prior each execution of the .IR statement , the .B collect clause evaluates .I expr and replaces .B var with a new bag which contains that value, as if by the expression .IB var " = list_add(" var ", " expr ) \fR.\fP When the loop terminates, .I var is replaced with a list formed from the bag which it used to hold, as if by .IB var " = list_end(" var ) \fR.\fP The effect is that .I var ends up with a list of the values of .I expr that were sampled before each iteration of the loop. The .B collect_plus clause is almost exactly the same as .B collect except in regard to the final behavior. When the loop terminates, .B collect_plus collects the value of .I expr one more time prior to the conversion to list. The effect is that .I var ends up with a list of all the values of .I expr that were sampled before each iteration of the loop, as well as one more sample of .I expr taken after loop termination. .SH LOOP CLAUSES: CALCULATION .bk .SS Loop clause \fIsumming\fP .bk .B Syntax: .ft B summing (\fIvar\fP, \fIexpr\fP) .ft R .B Description: The .B summing clause calculates the sum of the values of .I expr over the course of the loop. The clause contains no provision for termination; if the only clause in a .B loop is .B summing then it will not terminate. The .B summing clause initializes .B var to zero. Prior to each execution of the loop's .IR statement , .I expr is evaluated and its value added to to .IR var . The effect is that after the loop terminates, .I var ends up with the sum of the samples of the value of .I expr from before each iteration of the loop. .SS Loop clause \fIcounting\fP .bk .B Syntax: .ft B counting (\fIvar\fP, \fIexpr\fP) .ft R .B Description The .B counting clause initialized .I var to zero. Prior to each iteration of the loop, .I expr is evaluated and if it yields a true value, then .I var is incremented. Thus, .I var ends up with a count of the number of iterations in which .I expr was true. .SS Loop clauses \fIminimizing\fP and \fImaximizing\fP .bk .B Syntax: .ft B maximizing (\fIvar\fP, \fIexpr\fP) minimizing (\fIvar\fP, \fIexpr\fP) .ft R .B Description: The .B minimizing and .B maximizing clauses initialize .I var to the value .IR nil . (See the .B cppawk-cons manual page for .BR ). Prior to each execution of the loop .IR statement , .I var is updated as follows. If .I var is .BR nil , then it receives the value of .IR expr , thereby establishing that value as the hitherto calculated minimum or maximum. If .I var is not already .BR nil , then .B minimize updates it with the value of .I expr if that value is smaller than .IR var , and similarly, .B maximize replaces .I var with the value of .I expr if that value is greater than .IR var . Neither .B minimize nor .B maximize bring about loop termination. The effect of these clauses it to calculate the minimum or maximum observed of value of .I expr as sampled before each execution of the loop statement. If the loop never executes the .IR statement , then .I var retains the .B nil value indicating that no minimum or maximum had been found. .SS Loop clauses \fIargmax\fP and \fIargmin\fP .bk .B Syntax: .ft B argmax (\fImaxvar\fP, \fIarg\fP, \fIexpr\fP) argmin (\fIminvar\fP, \fIarg\fP, \fIexpr\fP) .ft R .B Description: The .B argmax and .B argmin clauses calculate the value of the expression .I arg which is observed when the maximum or minimum value of .I expr occurs. This value of .I arg associated with the maximum or minimum value of .I expr then appears in .I maxvar or .IR minvar respectively. (The actual maximum or minimum value of .I expr is itself not made available.) The .B argmax and .B argmin operations are most useful when .I arg and .I expr are related, such as .I expr being a function of .IR arg . For instance .I expr might be .BI sin( x ) and .I arg might be .IR x . This is the situation which inspires the term "argmax": .I arg is the argument of the .I expr function. This is not a requirement, though: .I arg and .I expr might simply be independent properties of the same datum. For example, .I arg might be .BI miles_per_gallon( car ) and .I expr might be .BI trunk_space( car ) in which case .BI argmax( mpg ", miles_per_gallon(" car "), trunk_space(" car )) will calculate and store into .I mpg the miles per gallon value of the car which has the maximum trunk space, assuming that the loop will step the value of .I car through a sequence of different car objects. Like .B minimize and .BR maximize , these clauses never bring about loop termination. First, .I minvar or .I maxvar is initialized to the .B nil value. (See the .B cppawk-cons manual page for .BR ). If the loop .I statement never executes, then these variables retain the .B nil value to indicate that no argument maximum or minimum was calculated. Prior to each execution of .IR statement , .I expr is evaluated. If it is the first iteration, then .I maxvar or .I minvar is set to the value of .IR arg . If it is the second or subsequent iteration, then .B argmax sets .I maxvar to the value of .I arg if .I expr is higher than the previously seen maximum value of .IR expr . Likewise, .B argmin sets .I minvar to the value of .I arg if .I expr is lower than the previously seen minimum value of .IR expr . .B Example: Find the values of .I x where the expression .BI sin( x ") * cos(" x ) has a maximum and minimum value, over the .I x range 0 to 3.14159 examined in increments of 0.001. .ft B #include BEGIN { loop (range_step (\fIx\fP, 0, 3.14159, 0.001), argmax (\fImx\fP, \fIx\fP, sin(\fIx\fP) * cos(\fIx\fP)), argmin (\fImi\fP, \fIx\fP, sin(\fIx\fP) * cos(\fIx\fP))) ; // empty print "max x =", \fImx\fP print "min x =", \fImi\fP } .ft R Output: .ft B max x = 0.785 min x = 2.356 .ft R .SH LOOP CLAUSES: TERMINATION CONTROL .bk .SS Loop clauses \fIwhile\fP and \fIuntil\fP .bk .B Syntax: .ft B while (\fIexpr\fP) until (\fIexpr\fP) .ft R .B Description: The .B while and .B until clauses provide a termination test to the loop. Prior to each iteration, .I expr is evaluated. Under the .B while clause, if .I expr is false. the loop terminates. Under the .B until clause, if .I expr is true, the loop terminates. Loop terminations are short circuited among parallel clauses. So that is to say, if an earlier clause indicates loop termination, then the termination tests of later clauses are not performed. Moreover, the preparation actions of .B no clause are performed when the loop terminates; only if it has been confirmed that the .I statement is going to be executed, due to the termination tests from all clauses reporting false, are the preparation actions executed. Therefore, in any iteration, later termination tests can rely on earlier termination tests having executed. For instance, if the success of an earlier termination test implies that a certain variable is safe to use in certain way, then a later termination test may use it in that way. Likewise, loop preparations may rely on all termination tests having executed. All tests in loop, including .B while and .B until are top-of-loop tests: tests carried out before every iteration, including the first. A bottom-of-loop test is one which is carried out after each iteration, which is logically equivalent to a top-of-loop test which is unconditionally true before the first iteration, and then turns into a .I "bona fide" test. A bottom-of-loop testing version of .B while or .B until isn't provided in .B but can be developed as an application-defined clause. It may also be simulated with the help of the first_then clause, according to this pattern: .ft B loop_for (first_then (\fIfirst_iter\fP, 1, 0), while (\fIfirst_iter\fP || \fIother_condition\fP)) statement .ft R Here, the .B first_iter flag is initialized to 1, and then after the first iteration steps to 0. Therefore the .B while clause's test is always true before the first iteration, and .I other_condition isn't tested. .SH LOOP CLAUSES: COMBINATORS .bk .SS Loop clause \fIparallel\fP .bk .B Syntax: .ft B parallel (\fIclause1\fP, \fIclause2\fP, ...) .ft R .B Description: The .B parallel construct may be used in the .B loop_nest macro, to indicate groups of clauses that should not be nested but treated in parallel. The .B parallel clause takes one or more arguments which are loop clauses. It arranges for the argument clauses to be performed in parallel, just like the way clauses are treated by the .B loop construct. For instance, the structure: .ft B loop_nest (\fIclause1\fP, parallel (\fIclause2\fP, \fIclause3\fP), \fIclause4\fP) \fIstatement\fP .ft R may be understood as equivalent to: .ft B loop (\fIclause1\fP) loop (\fIclause2\fP, \fIclause3\fP) loop (\fIclause4\fP) \fIstatement\fP .ft R .SS Loop clause \fIif\fP .bk .B Syntax: .ft B if (\fItest\fP, \fIclause\fP) .ft R .B Description: The .B if clause activates or deactivates the contained .I clause based on the value of the .I test expression. Firstly, the initializations of .I clause are performed unconditionally, as if it were not embedded in .BR if . Prior to every iteration, if the .I test expression is false, then .IR clause 's tests are not performed, and are assumed to be true. Thus while .I test is true, .I clause is prevented from being able to terminate the loop. Secondly, prior to the execution of the loop .IR statement , .I test is evaluated again. If the expression is false, then the preparation actions of .I clause are skipped. Lastly, prior to the execution of the iteration step actions. expression is false, then the step actions of .I clause are skipped. Effectively, the .I clause is suspended while the .I test expression is false. .B Example: Print a row number before the first element of every row. While this specific program can be coded much more succinctly, the goal is to demonstrate how the .B first_then clause is activated by the the condition .IB i " % 10 ==" .BR 1 . .ft B #include function row(\fIpg\fP) { if (\fIpg\fP > 1) print printf "r%03d", \fIpg\fP return \fIpg\fP } BEGIN { loop (range(\fIi\fP, 1, 100), if (\fIi\fP % 10 == 1, first_then(\fIpg\fP, row(1), row(\fIpg\fP + 1)))) printf " %3d", \fIi\fP } .ft R .B Output: .ft B r001 1 2 3 4 5 6 7 8 9 10 r002 11 12 13 14 15 16 17 18 19 20 r003 21 22 23 24 25 26 27 28 29 30 r004 31 32 33 34 35 36 37 38 39 40 r005 41 42 43 44 45 46 47 48 49 50 r006 51 52 53 54 55 56 57 58 59 60 r007 61 62 63 64 65 66 67 68 69 70 r008 71 72 73 74 75 76 77 78 79 80 r009 81 82 83 84 85 86 87 88 89 90 r010 91 92 93 94 95 96 97 98 99 100 .ft R .SH USER-DEFINED CLAUSES .bk It is possible to define new clauses for the .B loop macro, in application code. .SS Definition via Macro One method by which a user defined .B loop clause is possible is by writing it as a macro. This is because clauses look like macro invocations and are susceptible to expansion. .B Example: Introduce a .BI repeat( n ) clause that repeats .I n times, where .I n is an expression. .ft B #define repeat(n) range(repeat_counter_ ## __LINE__, 1, (n)) .ft R .SS Primary Definition An entirely new loop clause is developed by writing six macros, one of which is required only if the .I egawk (Enhanced GNU Awk) implementation of Awk is being used. The macros have names which are derived from the name of the clause. For example, to implement a clause called .BR myclause , the following macros must be written: .BR __temp_myclause , .BR __init_myclause , .BR __test_myclause , .BR __prep_myclause , .B __fini_myclause and .BR __step_myclause . The .BR __temp_myclause macro is not used unless the Awk implementation is .I egawk . All six macros must accept exactly the same arguments, and those will be the arguments that the clause will accept. They are described next: .IP \fB__temp_\fP The .I temp macro must expand to a comma-terminated list of temporary variable names which are needed by the clause. If the clause needs no hidden temporary variables, then this must expand to a terminating comma. Under the .I egawk implementation, these variables will be accumulated into a .B @let construct which precedes the loop, so that they are introduced as lexical variables visible only inside the loop. .IP \fB__init_\fP The .I init macro must expand to an expression which performs variable initializations. If the clause requires no initializations, its expansion must be the numeric token .BR 1 . .IP \fB__test_\fP The .I test macro must expand to an expression whose value is true if, and only if, the clause wishes the loop to terminate. If the clause does not terminate the loop, the expansion of this macro must be the numeric token .BR 1 . .IP \fB__prep_\fP The .I prep macro must expand to an expression that the clause needs to evaluate prior to the execution of the loop's iteration statement. This is evaluated only if all clauses have indicated that the loop isn't terminating, and hence the statement is going to be executed. .IP \fB__fini_\fP The .I fini macro must expand to an expression that the clause needs to evaluate in the situation when the loop terminates. If any termination test from any clause of a .B loop indicates that the loop must terminate, then the loop .I statement will not be executed any more; instead, the .I fini expressions of all the clauses will be evaluated, and then the loop ends. If a clause does not have any .I fini action, then this macro must expand to the token .BR 1 . .IP \fB__step_\fP The .I step macro must expand to an expression which is evaluated after every execution of the loop .I statement , in order to prepare new values of loop variables for the next iteration. Here is where numeric step variables are incremented and so forth. If the clause doesn't step, then this must expand to .BR 1 . .SS Example: \fBnull\fP clause Suppose we wish to define a clause called .B null which takes no arguments and does nothing. A loop which contains only this clause iterates forever. If the clause is added to any .BR loop , the semantics remains unchanged. The entire implementation is this: .ft B #include #define __temp_null , #define __init_null 1 #define __test_null 1 #define __prep_null 1 #define __fini_null 1 #define __step_null 1 BEGIN { loop (range (i, 1, 5), null) // does nothing print i } .ft R .SS Example: alpha-numeric stepping. .bk Suppose we have a function .BI nxstr( s ", " u ) which behaves as follows, on these example inputs: .ft B nxstr("000", "999") -> "001" nxstr("007", "777") -> "010" nxstr("abc", "zzz") -> "abd" nxstr("xxx", "yyy") -> "xxy" nxstr("xxy", "yyy") -> "xya" nxstr("yyx", "yyy") -> "yyy" nxstr("yyy", "yyy") -> 0 .ft R The function .BI nxstr implements a relation that could could be called "alpha-numeric step", where the second argument indicates limiting characters. A precise specification follows. Firstly, both .I s and .I u are alphanumeric strings of equal length, consisting of nothing but digits or the 26 letters of the English alphabet, in either upper or lower case. Furthermore, for every character in .IR s , the corresponding character in .I u is in the same category: digit, lower case or upper case, and that corresponding character has a rank at least as high. For instance, where .I s has the character .B p , .I u may have the characters .BR p , .BR q , .BR r ... but not .B o because .B o has a lower rank, and not .B X or .B 7 because they are in a different category. The .I u argument gives an upper limit. If .I s is identical to .I u then .B nxstr returns 0. Otherwise .B nxstr returns the next alphanumeric string derived from .I s as follows: if the last character is equal to the corresponding one in .I s then it is reset to the leading element of the category, otherwise it is replaced by its successor. In the case when the character is reset, the procedure is repeated with the character to the left, to increment the next digit. If that one is reset, then again, to the left and so forth. We would like to implement a loop clause which steps a variable .I s through a range of strings, as in .BI alpha_range( s ", \(dqaa0\(dq, \(dqcc9\(dq" ) to step through the strings "aa0", "aa1", ... "aa9", "ab0", ... "ab9", ... "cc0", ... "cc9". .ft B #include \fI// ... implementation of nxstr goes here ...\fP #define __temp_alpha_range(\fIs\fP, \fIfrom\fP, \fIto\fP) 1 #define __init_alpha_range(\fIs\fP, \fIfrom\fP, \fIto\fP) \fIs\fP = \fIfrom\fP #define __test_alpha_range(\fIs\fP, \fIfrom\fP, \fIto\fP) \fIs\fP #define __prep_alpha_range(\fIs\fP, \fIfrom\fP, \fIto\fP) 1 #define __fini_alpha_range(\fIs\fP, \fIfrom\fP, \fIto\fP) 1 #define __step_alpha_range(\fIs\fP, \fIfrom\fP, \fIto\fP) \fIs\fP = nxstr(\fIs\fP, \fIto\fP) BEGIN { loop (alpha_range (\fIx\fP, "aa0", "cc9")) print \fIx\fP } .ft R A working implementation of .B nxstr follows: .ft B // "register nextchar" function rn(x, y, c) { nextchar[x] = y if (y in nextchar) { resetchar[x] = y for (c = y; nextchar[c] != y; c = nextchar[c]) resetchar[c] = y } } BEGIN { rn("0", "1"); rn("1", "2"); rn("2", "3"); rn("3", "4"); rn("4", "5"); rn("5", "6"); rn("6", "7"); rn("7", "8"); rn("8", "9"); rn("9", "0"); rn("a", "b"); rn("b", "c"); rn("c", "d"); rn("d", "e"); rn("e", "f"); rn("f", "g"); rn("g", "h"); rn("h", "i"); rn("i", "j"); rn("j", "k"); rn("k", "l"); rn("l", "m"); rn("m", "n"); rn("n", "o"); rn("o", "p"); rn("p", "q"); rn("q", "r"); rn("r", "s"); rn("s", "t"); rn("t", "u"); rn("u", "v"); rn("v", "w"); rn("w", "x"); rn("x", "y"); rn("y", "z"); rn("z", "a"); rn("A", "B"); rn("B", "C"); rn("C", "D"); rn("D", "E"); rn("E", "F"); rn("F", "G"); rn("G", "H"); rn("H", "I"); rn("I", "J"); rn("J", "K"); rn("K", "L"); rn("L", "M"); rn("M", "N"); rn("N", "O"); rn("O", "P"); rn("P", "Q"); rn("Q", "R"); rn("R", "S"); rn("S", "T"); rn("T", "U"); rn("U", "V"); rn("V", "W"); rn("W", "X"); rn("X", "Y"); rn("Y", "Z"); rn("Z", "A"); } function nxstr(str, upto, l, sdig, udig, nxdig) { if (str == upto) return 0 len = length(str) for (; len > 0; --len) { sdig = substr(str, len, 1) udig = substr(upto, len, 1) if (sdig == udig) nxdig = resetchar[sdig] else nxdig = nextchar[sdig] str = substr(str, 1, len - 1) nxdig substr(str, len + 1) if (sdig != udig) break } return str } .ft R .SH "SEE ALSO" cppawk(1) .SH BUGS The .B parallel clause cannot be used in .BR loop , which prevents it from being useful in macro implementations of clauses. This is because it relies on a macro that is also being used in the expansion of .B loop . This issue is discussed in the BUGS section of the main .B cppawk man page. .SH AUTHOR Kaz Kylheku .SH COPYRIGHT Copyright 2022, BSD2 License.