foreach.inc — array "walker" macro

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. many loops can now be replaced with a simple call to Foreach(). for instance, given some array 'A' and an 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.

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.
the default walk is '0'. note walks '1' and '2' are identical when the dimensions are immediate neighbours.
.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[,..,last],elem) yes no 1, 2 yes arguments for all dimensions of array
(first[,..,last],elem,arg) yes yes 1, 2 yes
.Arg: "name"
'.Arg's role is to provide a dictionary (string) name to the payload macro when it gets called. the key requires the '.Extra' flag, and it does not have a default. see '.Macro' above.
.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 parameters, in order. otherwise the '.From' dimension index, and perhaps '.To', get used. '.Indices' can be used in conjunction with '.Extra' and a '.Walk' mode only. default 'false', see '.Macro' above.
.Boolean: t/f
when enabled indicates that the payload macro returns a boolean value; ie a zero return 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.
  ------------------------------------------------------------------------