the 'foreach.inc' file provides the Foreach()
macro for the relatively common task of iterating through an array,
and processing the current element using a macro.
one usually needs to write #for()
or #while()
loops,
nested several deep,
to process multi-dimensional arrays.
the purpose of the "foreach" include
is to replace many such loop constructs with simple calls
to the Foreach()
macro.
for instance, given some array 'A' and
an associated macro 'doXYZ',
and relying on the code's defaults,
you can write:
Foreach(A, dictionary {.Macro: "doXYZ"})
the 'dictionary {}
'
contains all of the variable,
user-defined "stuff".
there are several key/value pairs provided
to adjust the macro's behaviour,
but only one,
the name of your macro (aka the "payload"),
is required.
what said macro actually does is,
as far as I can tell,
limited only by the imagination (and the data!).
the various examples shown merely scratch the surface.
another small ("fringe") benefit derives from using
Foreach()
: whether you add a couple of elements
to your array, or delete ten,
the supporting code (ie the payload) will,
in all likelihood,
never need adapting to the new number of elements.
the Foreach()
macro 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.
note – the code examples mostly use short, lowercase
identifiers
i_
, j_
etc for index variables,
with an underscore to avoid name clashes like 'x'.
the current array element is always elem_
.
A
and D
are used for arrays and dictionaries, respectively.
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 two globally "visible" identifiers:
one macro, one variable.
there are however others,
and all visible identifiers except
Foreach()
are prefixed 'fore_'.
fore_debug
this optional variable can be declared to make Foreach()
print some extra information,
including a header summarising the macro's arguments.
fore_debug
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.
Foreach()
returns nothing,
unless your 'void'-type payload macro returns
some value.
note – the choice of 'void', with hindsight,
is very unfortunate because it is misleading;
I have decided to stick with the term however,
hoping this explanation will .. make good.
as mentioned above, the dictionary requires a '.Macro' key – it has no default. all other keys are given "sensible" default 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 every element in the '.From' dimension, visit each 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 such a value ("void").
all payload macros are called with two or more arguments
("parameters"),
depending on a combination of '.Extra' and
'.Indices', '.Arg',
and '.Walk'.
payload macro signatures macro arguments extra indices arg walk comment (first,elem) no no no 0, 1, 2 'first' is the current '.From' index
'elem' is the current array element(first,elem,arg) no no yes 0, 1, 2 'arg' is always the last argument (first,last,elem) yes no no 1, 2 'last' is the current '.To' index (first,last,elem,arg) yes no yes 1, 2 (first[,..,last],elem) yes yes no 1, 2 index parameters for all dimensions of array (first[,..,last],elem,arg) yes yes yes 1, 2 elem_
modifies the underlying array. - .Arg: "name"
- '.Arg's role is to provide the (string) name of a dictionary to the payload macro when it gets called. the purpose of the dictionary is to enable a separate "channel of communication" in the payload; see '.Macro' above. this key does not have a default.
- .Extra: t/f
- when enabled, this flag indicates the addition of one (usually) or more array index arguments in the payload macro's signature; see '.Macro' above. default 'false'.
- .Indices: t/f
- this flag controls which of the array indices will be passed as arguments to the payload macro. when enabled, all of the array's dimensions indices are included as the payload's parameters, in order. without, the '.From' dimension index, and perhaps '.To', get used. '.Indices' can be used in conjunction with '.Extra' and a '.Walk' mode only, see '.Macro' above. default 'false'.
- .Boolean: t/f
- when enabled indicates that the payload macro returns a boolean type value, where a zero means 'false' and any other (numeric) value stands for 'true'; the POV-Ray built-in 'yes'/'no' etc keywords work too, of course. default disabled.
- .Strict: t/f
-
this flag controls how
Foreach()
reacts when a '.Boolean' payload macro returns 'false'. when enabled,Foreach()
halts execution of the payload immediately on a 'false' return, and an info message will be printed. when disabled, the default, execution of the payload continues.
note 'void' type payloads can use#break
to terminate early, irrespective of '.Strict'. there are, however, two differences. first, a#break
terminates the current run of the payload only, notForeach()
as '.Strict' does. second, there is no corresponding "break after call" message. - .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, your own key names ought to be capitalised, like the above.
when running Foreach()
, a number of errors can occur.
all usage error type messages are prefixed 'oops',
with a couple of exceptions.
when the supplied array is empty,
or when the payload macro returns a value but the
'.Boolean' flag is not enabled (or vice versa),
the errors are caught by POV-Ray and you'll see
"normal" messages.
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()').
(tutorial) examples
the snippets below show code and the corresponding outputs, using an "unofficial" 3.8.0.alpha version of POV-Ray. in addition to the functionality demonstrated here, the included 'fore_demo.pov' scene contains examples covering the basic (usage) options.
the same five-dimensional array is used in all examples below.
it is as small as possible while still allowing .. varied
examples;
the "data" is simply the integer sequence from
1
to (2*2*2*2*3)
:
#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}}}} };
to demonstrate the three walk modes, a one-line
('void' type) payload macro is used,
which displays the values it is passed.
the default walk is '0'
(visit all elements in '.From'),
so a "minimalist"
call will do:
#macro m_echo(i_,elem_) #debug concat("m_echo i = ",str(i_,0,0),", elem = ",str(elem_,0,0),".\n") #end Foreach(A, dictionary {.Macro: "m_echo", .Verbose: on})
-----[Foreach]---------------------------------------------------------- first dimension: 1 n'th dimension: 1 walk n'th dimension: no walk through to n'th: no payload macro: m_echo extra index: no all indices: no payload arg: no returns boolean: no break on 'false': no m_echo i = 0, elem = 1. m_echo i = 1, elem = 25. ------------------------------------------------------------------------
writing the dictionary inline as above is a very convenient way of calling the macro, if is to be used but once. on the other hand, declaring a dictionary is more convenient for reuse, as often only one or two of the keys will need changing. this snippet compares walk modes '1' and '2', using the first three dimensions of array:
#declare D = dictionary { .Macro: "m_echo", .Walk: 1, .To: 3, .Verbose: on }; Foreach(A, D) #declare D.Walk = 2; Foreach(A, D)
-----[Foreach]---------------------------------------------------------- first dimension: 1 n'th dimension: 3 walk n'th dimension: yes walk through to n'th: no payload macro: m_echo extra index: no all indices: no payload arg: no returns boolean: no break on 'false': no m_echo i = 0, elem = 1. m_echo i = 0, elem = 7. m_echo i = 1, elem = 25. m_echo i = 1, elem = 31. ------------------------------------------------------------------------ -----[Foreach]---------------------------------------------------------- first dimension: 1 n'th dimension: 3 walk n'th dimension: no walk through to n'th: yes payload macro: m_echo extra index: no all indices: no payload arg: no returns boolean: no break on 'false': no m_echo i = 0, elem = 1. m_echo i = 0, elem = 7. m_echo i = 0, elem = 13. m_echo i = 0, elem = 19. m_echo i = 1, elem = 25. m_echo i = 1, elem = 31. m_echo i = 1, elem = 37. m_echo i = 1, elem = 43. ------------------------------------------------------------------------
the last '.Walk' mode example contrasts the
"conventional way of doing"
and the Foreach()
equivalent:
#for (i1, 0, dimension_size(A,1)-1) #for (i2, 0, dimension_size(A,2)-1) #for (i3, 0, dimension_size(A,3)-1) #for (i4, 0, dimension_size(A,4)-1) #for (i5, 0, dimension_size(A,5)-1) m_echo(i1, A[i1][i2][i3][i4][i5]) #end #end #end #end #end Foreach(A, dictionary { .Macro: "m_echo", .Walk: 2, .To: 5, .Verbose: on })
m_echo i = 0, elem = 1. m_echo i = 0, elem = 2. ... m_echo i = 1, elem = 47. m_echo i = 1, elem = 48. -----[Foreach]---------------------------------------------------------- first dimension: 1 n'th dimension: 5 walk n'th dimension: no walk through to n'th: yes payload macro: m_echo extra index: no all indices: no payload arg: no returns boolean: no break on 'false': no m_echo i = 0, elem = 1. m_echo i = 0, elem = 2. ... m_echo i = 1, elem = 47. m_echo i = 1, elem = 48. ------------------------------------------------------------------------
the '.Walk' examples show that in many cases the payload
macro does not need to know the exact location of an element in the
array to "do its thing".
when that knowledge becomes useful or necessary,
you can use the '.Indices' option to have
Foreach()
add parameters,
for the complete set of current indices,
to the payload macro call.
as mentioned above,
'.Indices' requires both the
'.Extra' flag and a
'.Walk' mode other than the default.
walking the first three dimensions, and as before, the payloads
simply echo their arguments:
#macro m_2arg(i_,elem_) #debug concat("m_2arg i = ",str(i_,0,0),", elem = ",str(elem_,0,0),".\n") #end #macro m_3arg(i_,j_,elem_) #debug concat("m_3arg i = ",str(i_,0,0),", j = ",str(j_,0,0), ", elem = ",str(elem_,0,0),".\n") #end #macro m_6arg(i_,j_,k_,l_,m_,elem_) #debug concat("m_6arg i = ",str(i_,0,0),", j = ",str(j_,0,0), ", k = ",str(k_,0,0),", l = ",str(l_,0,0), ", m = ",str(m_,0,0),", elem = ",str(elem_,0,0),".\n") #end #declare D = dictionary { .Macro: "m_2arg", .Walk: 2, .To: 3, .Verbose: on }; Foreach(A, D) #declare D.Macro = "m_3arg"; #declare D.Extra = on; Foreach(A, D) #declare D.Macro = "m_6arg"; #declare D.Indices = on; Foreach(A, D)
-----[Foreach]---------------------------------------------------------- first dimension: 1 n'th dimension: 3 walk n'th dimension: no walk through to n'th: yes payload macro: m_2arg extra index: no all indices: no payload arg: no returns boolean: no break on 'false': no m_2arg i = 0, elem = 1. m_2arg i = 0, elem = 7. m_2arg i = 0, elem = 13. m_2arg i = 0, elem = 19. m_2arg i = 1, elem = 25. m_2arg i = 1, elem = 31. m_2arg i = 1, elem = 37. m_2arg i = 1, elem = 43. ------------------------------------------------------------------------ -----[Foreach]---------------------------------------------------------- first dimension: 1 n'th dimension: 3 walk n'th dimension: no walk through to n'th: yes payload macro: m_3arg extra index: yes all indices: no payload arg: no returns boolean: no break on 'false': no m_3arg i = 0, j = 0, elem = 1. m_3arg i = 0, j = 1, elem = 7. m_3arg i = 0, j = 0, elem = 13. m_3arg i = 0, j = 1, elem = 19. m_3arg i = 1, j = 0, elem = 25. m_3arg i = 1, j = 1, elem = 31. m_3arg i = 1, j = 0, elem = 37. m_3arg i = 1, j = 1, elem = 43. ------------------------------------------------------------------------ -----[Foreach]---------------------------------------------------------- first dimension: 1 n'th dimension: 3 walk n'th dimension: no walk through to n'th: yes payload macro: m_6arg extra index: yes all indices: yes payload arg: no returns boolean: no break on 'false': no m_6arg i = 0, j = 0, k = 0, l = 0, m = 0, elem = 1. m_6arg i = 0, j = 0, k = 1, l = 0, m = 0, elem = 7. m_6arg i = 0, j = 1, k = 0, l = 0, m = 0, elem = 13. m_6arg i = 0, j = 1, k = 1, l = 0, m = 0, elem = 19. m_6arg i = 1, j = 0, k = 0, l = 0, m = 0, elem = 25. m_6arg i = 1, j = 0, k = 1, l = 0, m = 0, elem = 31. m_6arg i = 1, j = 1, k = 0, l = 0, m = 0, elem = 37. m_6arg i = 1, j = 1, k = 1, l = 0, m = 0, elem = 43. ------------------------------------------------------------------------
the dictionary provided via the '.Arg' key is available
for both input and output. in a sense a payload macro using this
option can act much like a "traditional" callback in
other computer languages.
the example demonstrates this "IO mechanism" with a payload
that can be made to double its result:
#macro m_add(i_,elem_,arg_) #local arg_.sum_ = arg_.sum_ + elem_ + (arg_.double_ ? elem_ : 0); #end #declare D = dictionary { .Macro: "m_add", .Walk: 2, .To: 5, .Arg: "R", .Verbose: on }; #declare R = dictionary {.double_: off, .sum_: 0}; Foreach(A, D) #debug concat("sum of elements is ",str(R.sum_,0,0),".\n") #declare D.Verbose = off; #declare R.double_ = on; #declare R.sum_ = 0; Foreach(A, D) #debug concat("twice sum of elements is ",str(R.sum_,0,0),".\n")
-----[Foreach]---------------------------------------------------------- first dimension: 1 n'th dimension: 5 walk n'th dimension: no walk through to n'th: yes payload macro: m_add extra index: no all indices: no payload arg: yes returns boolean: no break on 'false': no ------------------------------------------------------------------------ sum of elements is 1176. twice sum of elements is 2352.
up to this point, none of the payload macros explicitly
returned a value.
the final tutorial example presents the other type macro,
one returning a boolean value which, in this case,
indicates whether the current array element divides by
some number.
on the first run, execution of Foreach()
is not stopped when the payload returns 'false'.
'.Strict' then is set for the second run,
leading to an immediate halt.
'.Verbose' is not used because of the many
"false return from ..." messages otherwise.
#macro m_divby(i_,elem_,arg_) #local r_ = (mod(elem_,arg_.n_) ? false : true); #if (r_) #debug concat(str(elem_,0,0)," divides by ",str(arg_.n_,0,0),".\n") #end r_ #end #local D = dictionary { .Macro: "m_divby", .Walk: 2, .To: 5, .Boolean: on, .Arg: "R" }; #declare R = dictionary {.n_: 11}; Foreach(A, D) #debug "\n" #declare D.Strict = on; Foreach(A, D)
11 divides by 11. 22 divides by 11. 33 divides by 11. 44 divides by 11. break after call 'm_divby(0,a_[0][0][0][0][0],R)'.
the concluding (code) example is a complete
copy'n'paste scene.
it shows nested Foreach()
use,
and is based on a different data array
from the previous snippets.
the message text is deliberately obscured until rendered, for
added fun.
(can you work out the text before running the code?)
#version 3.8; global_settings {assumed_gamma 1} #include "foreach.inc" #include "logo.inc" camera { location <3.25,0,-4.5> direction z right x * (4/3) up y angle 75 look_at <3.25,0,0> } light_source {<-1.5,1,-1> * 10e3 color srgb 1 parallel} /* the data. * elem {line begin, scale, font index, array char data}. */ #declare array_ = array [4] { array mixed {<.1, 1.6, 0>, .85, 2, array {70, 112, 116, 104, 101, 104, 110, 47, 49}}, array mixed {<0, .2, 0>, .75, 0, array {109, 98, 102, 104, 36, 122, 121, 112, 118, 112}}, array mixed {<0, -1, 0>, 1, 1, array {80, 80, 88, 48, 86, 102, 127}}, array mixed {<0, -2, 0>, .75, 0, array {51, 47, 58, 49, 52, 50, 103, 115, 120, 113, 107, 57}} }; #declare font_ = array [3] {"comic.ttf", "comicbd.ttf", "cour.ttf"}; /* et voilĂ */ object { Povray_Logo_Prism texture { pigment {color rgb <1,1,0>} normal {wrinkles scale .2} finish {specular .4} } scale 1.5 translate <5.3, 0, 0> } #macro m_ch(i_,elem_) chr(elem_ - i_) #end #macro m_text(i_,elem_) #local s_ = concat(Foreach(elem_[3], dictionary {.Macro: "m_ch"})); text { ttf font_[elem_[2]] s_, .1, 0 texture { pigment {color rgb <1,1,0>} normal {dents scale .0005} finish {specular .8} } scale (<1,1,1> * elem_[1]) translate elem_[0] } #end Foreach(array_, dictionary {.Macro: "m_text"})
"The End". enjoy.