the 'foreach.inc' file provides the
Foreach()
macro for the relatively common task of
iterating an array and processing the current element in a macro.
usually this means writing #while()
or
#for()
loops, nested for multi-dimensional arrays.
with Foreach()
loops become .. redundant.
given an array 'A' and some associated macro
'foo', it can be as simple as:
Foreach(A, dictionary {.Macro: "foo"})
the 'dictionary {}
' contains all the
variable, user-defined "stuff".
there are several key/value pairs to adjust the macro's
behaviour, but only one,
the name of your macro (aka the "payload"),
is required.
Foreach()
works with all POV-Ray pre version 3.8
arrays of up to five dimensions.
it also works for new-style, unsized arrays, for the first dimension;
support for nested unsized arrays may be added when the need arises.
the code is written for the Persistence of Vision Raytracer, version 3.8 or later.
foreach.inc
the file is a regular POV-Ray include, and stand-alone,
ie does not depend on other includes.
seen from the user's perspective,
it provides only two "visible" identifiers:
one macro, one variable.
there are however others,
and all visible identifiers except Foreach()
are prefixed 'fore_'.
fore_debug
optionally declare this boolean value to make Foreach()
print some extra information, including a summary of the macro's
arguments.
must be declared before calling the macro.
#declare fore_debug = on;
note this means one additional line of "called some element" output per (payload) macro invocation. :-)
Foreach(array, dictionary)
the macro can be used to process sized, one to five-dimensional arrays,
and basic unsized arrays. it returns (expands to) nothing.
calls to Foreach()
cannot be nested.
as mentioned above, the dictionary requires a '.Macro' key – there is no default. all other keys are given "sensible" values when omitted, except '.Arg'.
- .Walk: n
-
determines how elements in the array will be visited,
there are three distinct "modes":
- 0 - the payload macro is called for every element in the '.From' dimension. '.To' need not be given, as it is not used.
- 1 - for each element in the '.From' dimension, also visit every element in the '.To' dimension.
- 2 - visit all elements in the '.From' dimension through to the '.To' dimension, inclusive.
- .From: n
- selects the first, leftmost dimension to use. default '1'.
- .To: n
- specifies the last, rightmost dimension to use. defaults to the value of '.From'.
- .Macro: "name"
-
the (string) name of a macro you supply.
there are basically two types of payload macro,
ones which return/expand to a boolean value,
and ones which do not return a value ("void").
all payload macros are called with two or more arguments,
depending on a combination of '.Extra',
'.Indices' and '.Arg',
and of course '.Walk'.
payload macro signatures macro arguments extra arg walk indices comment (first,elem) no no 0, 1, 2 no 'first' is the current '.From' index
'elem' is the current array element(first,elem,arg) yes yes 0, 1, 2 no 'arg' is always the last argument (first,last,elem) yes no 1, 2 no 'last' is the current '.To' index (first,last,elem,arg) yes yes 1, 2 no (first,[second,..,]last,elem) yes no 1, 2 yes arguments for all dimensions of array (first,[second,..,]last,elem,arg) yes yes 1, 2 yes - .Arg: "name"
- '.Arg's role is to provide a dictionary name (string) argument to the payload macro when it gets called. requires the '.Extra' flag, see '.Macro' above. this key does not have a default.
- .Indices: t/f
- this flag controls which of the array indices will be passed as arguments to the payload macro. when enabled, all the array's dimensions indices are included in the payload's arguments, in order. otherwise the '.From' dimension index, and perhaps '.To', get used. '.Indices' is useful only in conjunction with '.Extra'. default 'false', see '.Macro' above.
- .Boolean: t/f
- when enabled indicates that the payload macro returns a boolean value; that is, a zero value means false, any other (numeric) value means true. default 'false'.
- .Strict: t/f
-
this flag controls whether
Foreach()
will terminate immediately when a 'bool' type payload macro returns 'false'. the default is 'false', that is, keep running.
note 'void' type payloads can use#break
to terminate early, irrespective of '.Strict'. there is, however, no corresponding "break after call" message. - .Extra: t/f
- this flag indicates a changed payload macro signature when enabled, see '.Macro' above. default 'false'.
- .Verbose: t/f
-
when enabled,
Foreach()
will print summary information to POV-Ray's debug stream. in addition, when a 'bool' type payload macro returns false, a "false return from" style message will be emitted. default 'false'.
it is ok to add your own keys to the dictionary. to avoid potential name clashes, own key names should be captialised, like the above.
when running Foreach()
, a number of errors can occur.
all usage error type messages are prefixed 'oops',
with one exception.
when the payload macro returns a value but the '.Boolean'
flag is not enabled (or vice versa), the error is caught by POV-Ray
and you'll see a "normal" message.
lastly, every time Foreach()
runs it leaves behind a file
named 'parse_fore.tmp', in the current working directory.
the code uses an adapted version of POV-Ray's
Parse_String()
macro (from 'strings.inc').
I recommend adding the path of a suitable "temp" directory
(see 'fn_' in 'foreach.inc:fore_exec()').
examples
the snippets below show code and corresponding outputs,
obtained by running the Foreach()
macro with an
array/payload combination and different '.Walk' settings.
using an "unofficial" 3.8.0.alpha version of POV-Ray.
only part of the functionality is illustrated here, the included
'fore_demo.pov' scene contains further examples of walks
and the other usage options.
to demonstrate the effects of choosing type of '.Walk', the following array is used:
#declare A = array [2][2][2][2][3] { {{{{1, 2, 3}, {4, 5, 6}}, {{7, 8, 9}, {10, 11, 12}}}, {{{13, 14, 15}, {16, 17, 18}}, {{19, 20, 21}, {22, 23, 24}}}}, {{{{25, 26, 27}, {28, 29, 30}}, {{31, 32, 33}, {34, 35, 36}}}, {{{37, 38, 39}, {40, 41, 42}}, {{43, 44, 45}, {46, 47, 48}}}} };
the demo ('void' type) payload macro simply displays
the values it is passed.
the default walk is '0', so a "minimalist"
call will do:
#macro foo(i_,n_) #debug concat("foo i = ",str(i_,0,0),", n = ",str(n_,0,0),".\n") #end Foreach(A, dictionary {.Macro: "foo", .Verbose: on})
-----[Foreach]---------------------------------------------------------- walk n'th dim: no walk thru to n'th: no first dim: 1 n'th dim: 1 payload macro: foo extra arg: no returns bool: no break on 'false': no foo i = 0, n = 1. foo i = 1, n = 25. ------------------------------------------------------------------------
writing the dictionary inline as above is the most convenient way of calling the macro, if is to be used but once. otoh, declaring a dictionary is very convenient for reuse, as often only one or two of the settings need changing:
#declare D = dictionary { .Walk : 1, .Macro : "foo", .From : 2, .To : 4, .Boolean : off, .Strict : off, .Extra : off, .Verbose : on }; Foreach(A,D) #declare D.Walk = 2; Foreach(A,D)
-----[Foreach]---------------------------------------------------------- walk n'th dim: yes walk thru to n'th: no first dim: 2 n'th dim: 4 payload macro: foo extra arg: no returns bool: no break on 'false': no foo i = 0, n = 1. foo i = 0, n = 4. foo i = 1, n = 13. foo i = 1, n = 16. ------------------------------------------------------------------------ -----[Foreach]---------------------------------------------------------- walk n'th dim: no walk thru to n'th: yes first dim: 2 n'th dim: 4 payload macro: foo extra arg: no returns bool: no break on 'false': no foo i = 0, n = 1. foo i = 0, n = 4. foo i = 0, n = 7. foo i = 0, n = 10. foo i = 1, n = 13. foo i = 1, n = 16. foo i = 1, n = 19. foo i = 1, n = 22. ------------------------------------------------------------------------
the last '.Walk' example simply contrasts the
"conventional way of doing"
with the equivalent Foreach()
:
#for (a_, 0, dimension_size(A,1)-1) #for (b_, 0, dimension_size(A,2)-1) #for (c_, 0, dimension_size(A,3)-1) #for (d_, 0, dimension_size(A,4)-1) #for (e_, 0, dimension_size(A,5)-1) foo(a_, A[a_][b_][c_][d_][e_]) #end #end #end #end #end Foreach(A, dictionary { .Macro: "foo", .Walk: 2, .To: 5, .Verbose: on })
foo i = 0, n = 1. foo i = 0, n = 2. ... foo i = 1, n = 47. foo i = 1, n = 48. -----[Foreach]---------------------------------------------------------- walk n'th dim: no walk thru to n'th: yes first dim: 1 n'th dim: 5 payload macro: foo extra arg: no returns bool: no break on 'false': no foo i = 0, n = 1. foo i = 0, n = 2. ... foo i = 1, n = 47. foo i = 1, n = 48. ------------------------------------------------------------------------