the 'foreach.inc' file provides the
Foreach()
macro for the relatively common task of
"walking" an array and processing elements in a macro.
usually this means writing #while()
or
#for()
loops, nested for multi-dimensional arrays.
given an array 'A' and some macro 'foo',
with Foreach()
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 old-style,
pre POV-Ray 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 only requires the '.Macro' key, which has no default. all other keys are given "sensible" values when omitted.
- .Walk: n
-
determines how the array will be walked,
there are three distinct "modes":
- 0 - the payload macro is called for every element in the '.From' dimension, the value of '.To' is irrelevant.
- 1 - for each element in the '.From' dimension, visit every element in the '.To' dimension.
- 2 -visit all elements from the '.From' dimension through to the '.To' dimension, inclusive.
- .From: n
- selects the first/leftmost dimension to use. default '1'.
- .To: n
- specifies the second, rightmost dimension to use. defaults to the value of '.From'.
- .Macro: "name"
- the (string) name of a macro you wrote/supply. there are two basic types of payload macro, ones which return/expand to a boolean value, and ones which do not return a value ("void"). payload macros are called with either two or three arguments, depending on the '.Extra' flag setting; the first argument is the current index in the '.From' dimension, the last argument is the value of the current array element, and the additional argument, the current index in the '.To' dimension, if present, between those.
- .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 payload macro returns 'false'. the default is 'false', that is, keep running. - .Extra: t/f
- this flag allows to change the payload macro's signature from two to three arguments (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 own keys to the dictionary. to avoid potential name clashes, own keys should have an uppercase initial in their names, just like the existing.
when running Foreach()
, a number of errors can occur.
all usage type error 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 (in 'strings.inc').
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 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. ------------------------------------------------------------------------