Reference:User Defined Macros

From POV-Wiki
Revision as of 14:28, 9 June 2021 by Clipka (talk | contribs) (canonicalize a version number)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

POV-Ray v3.1 introduced user defined macros with parameters. This feature, along with the ability to declare #local variables, turned the POV-Ray Language into a fully functional programming language. Consequently, it is now possible to write scene generation tools in POV-Ray's own language that previously required external utilities.

The macro Directive

The syntax for declaring a macro is:

MACRO_DEFINITION:
  #macro IDENTIFIER ([PARAM_IDENT] [, PARAM_IDENT]... ) TOKENS... #end
PARAM_IDENT:
  [optional] GENERIC_IDENTIFIER

Where IDENTIFIER is the name of the macro and PARAM_IDENT is a list of zero or more formal parameter identifiers separated by commas and enclosed by parentheses. The parentheses are required even if no parameters are specified. New in version 3.8 see also: Optional Parameters.

The TOKENS are any number of POV-Ray keyword, identifiers, or punctuation marks which are the body of the macro. The body of the macro may contain almost any POV-Ray syntax items you desire. It is terminated by the #end directive.

Note: Any conditional directives such as #if...#end, #while...#end, etc. must be fully nested inside or outside the macro so that the corresponding #end directives pair-up properly.

A macro must be declared before it is invoked. All macro names are global in scope and permanent in duration. You may redefine a macro by another #macro directive with the same name. The previous definition is lost. Macro names respond to #ifdef, #ifndef, and #undef directives.

See also:

Invoking Macros

You invoke the macro by specifying the macro name followed by a list of zero or more actual parameters enclosed in parentheses and separated by commas. The number of actual parameters must match the number of formal parameters in the definition. The parentheses are required even if no parameters are specified. The syntax is:

MACRO_INVOCATION:
  MACRO_IDENTIFIER ( [ACTUAL_PARAM] [, ACTUAL_PARAM]... )
ACTUAL_PARAM:
  IDENTIFIER | RVALUE

An RVALUE is any value that can legally appear to the right of an equals sign in a #declare or #local declaration. See Declaring identifiers for information on RVALUE. When the macro is invoked, a new local symbol table is created. The actual parameters are assigned to formal parameter identifiers as local, temporary variables. POV-Ray jumps to the body of the macro and continues parsing until the matching #end directive is reached. There, the local variables created by the parameters are destroyed as well as any local identifiers expressly created in the body of the macro. It then resumes parsing at the point where the macro was invoked. It is as though the body of the macro was cut and pasted into the scene at the point where the macro was invoked.

Note: It is possible to invoke a macro that was declared in another file. This is quite normal and in fact is how many plug-ins work (such as the popular Lens Flare macro). However, be aware that calling a macro that was declared in a file different from the one that it is being called from involves more overhead than calling one in the same file.

This is because POV-Ray does not tokenize and store its language. Calling a macro in another file therefore requires that the other file be opened and closed for each call. Normally, this overhead is inconsequential; however, if you are calling the macro many thousands of times, it can cause significant delays. A future version of the POV-Ray language will remove this problem.

Here is a simple macro that creates a window frame object when you specify the inner and outer dimensions.

#macro Make_Frame(OuterWidth,OuterHeight,InnerWidth,
        InnerHeight,Depth)
#local Horz = (OuterHeight-InnerHeight)/2;
#local Vert = (OuterWidth-InnerWidth)/2;
difference {
  box {
    <0,0,0>,<OuterWidth,OuterHeight,Depth>
    }
  box {
    <Vert,Horz,-0.1>,
    <OuterWidth-Vert,OuterHeight-Horz,Depth+0.1>
    }
  }
#end
Make_Frame(8,10,7,9,1) //invoke the macro

In this example, the macro has five float parameters. The actual parameters (the values 8, 10, 7, 9, and 1) are assigned to the five identifiers in the #macro formal parameter list. It is as though you had used the following five lines of code.

#local OuterWidth = 8;
#local OuterHeight = 10;
#local InnerWidth, = 7;
#local InnerHeight = 9;
#local Depth = 1;

These five identifiers are stored in the same symbol table as any other local identifier such as Horz or Vert in this example. The parameters and local variables are all destroyed when the #end statement is reached. See Identifier Name Collisions for a detailed discussion of how local identifiers, parameters, and global identifiers work when a local identifier has the same name as a previously declared identifier.

Are POV-Ray Macros a Function or a Macro?

POV-Ray macros are a strange mix of macros and functions. In traditional computer programming languages, a macro works entirely by token substitution. The body of the routine is inserted into the invocation point by simply copying the tokens and parsing them as if they had been cut and pasted in place. Such cut-and-paste substitution is often called macro substitution because it is what macros are all about. In this respect, POV-Ray macros are exactly like traditional macros in that they use macro substitution for the body of the macro. However traditional macros also use this cut-and-paste substitution strategy for parameters but POV-Ray does not.

Suppose you have a macro in the C programming language Typical_Cmac(Param) and you invoke it as Typical_Cmac(else A=B). Anywhere that Param appears in the macro body, the four tokens else, A, =, and B are substituted into the program code using a cut-and-paste operation. No type checking is performed because anything is legal. The ability to pass an arbitrary group of tokens via a macro parameter is a powerful (and sadly often abused) feature of traditional macros.

After careful deliberation, we have decided against this type of parameters for our macros. The reason is that POV-Ray uses commas more frequently in its syntax than do most programming languages. Suppose you create a macro that is designed to operate on one vector and two floats. It might be defined OurMac(V,F1,F2). If you allow arbitrary strings of tokens and invoke a macro such as OurMac(<1,2,3>,4,5) then it is impossible to tell if this is a vector and two floats or if its 5 parameters with the two tokens < and 1 as the first parameter. If we design the macro to accept 5 parameters then we cannot invoke it like this... OurMac(MyVector,4,5).

Function parameters in traditional programming languages do not use token substitution to pass values. They create temporary, local variables to store parameters that are either constant values or identifier references which are in effect a pointer to a variable. POV-Ray macros use this function-like system for passing parameters to its macros. In our example OurMac(<1,2,3>,4,5), POV-Ray sees the < and knows it must be the start of a vector. It parses the whole vector expression and assigns it to the first parameter exactly as though you had used the statement #local V=<1,2,3>;.

Although we say that POV-Ray parameters are more like traditional function parameters than macro parameters, there still is one difference. Most languages require you to declare the type of each parameter in the definition before you use it but POV-Ray does not. This should be no surprise because most languages require you to declare the type of any identifier before you use it but POV-Ray does not. This means that if you pass the wrong type value in a POV-Ray macro parameter, it may not generate an error until you reference the identifier in the macro body. No type checking is performed as the parameter is passed. So in this very limited respect, POV-Ray parameters are somewhat macro-like but are mostly function-like.

Returning a Value Like a Function

POV-Ray macros have a variety of uses. Like most macros, they provide a parameterized way to insert arbitrary code into a scene file. However most POV-Ray macros will be used like functions or procedures in a traditional programming language. Macros are designed to fill all of these roles.

When the body of a macro consists of statements that create an entire item such as an object, texture, etc. then the macro acts like a function which returns a single value. The Make_Frame macro example in the section Invoking Macros above is such a macro which returns a value that is an object. Here are some examples of how you might invoke it.

union {  //make a union of two objects
  object{ Make_Frame(8,10,7,9,1) translate  20*x}
  object{ Make_Frame(8,10,7,9,1) translate -20*x}
  }
#declare BigFrame = object{ Make_Frame(8,10,7,9,1)}
#declare SmallFrame = object{ Make_Frame(5,4,4,3,0.5)}

Because no type checking is performed on parameters and because the expression syntax for floats, vectors, and colors is identical, you can create clever macros which work on all three. See the sample scene MACRO3.POV which includes this macro to interpolate values.

// Define the macro.  Parameters are:
//   T:  Middle value of time
//   T1: Initial time
//   T2: Final time
//   P1: Initial position (may be float, vector or color)
//   P2: Final position (may be float, vector or color)
//   Result is a value between P1 and P2 in the same proportion
//    as T is between T1 and T2.
#macro Interpolate(T,T1,T2,P1,P2)
  (P1+(T1+T/(T2-T1))*(P2-P1))
#end

You might invoke it with P1 and P2 as floats, vectors, or colors as follows.

sphere{
  Interpolate(I,0,15,<2,3,4>,<9,8,7>), //center location is vector
  Interpolate(I,0,15,3.0,5.5)          //radius is float
  pigment {
    color Interpolate(I,0,15,rgb<1,1,0>,rgb<0,1,1>)
    }
  }

As the float value I varies from 0 to 15, the location, radius, and color of the sphere vary accordingly.

There is a danger in using macros as functions. In a traditional programming language function, the result to be returned is actually assigned to a temporary variable and the invoking code treats it as a variable of a given type. However macro substitution may result in invalid or undesired syntax. The definition of the macro Interpolate above has an outermost set of parentheses. If those parentheses are omitted, it will not matter in the examples above, but what if you do this...

#declare Value = Interpolate(I,0,15,3.0,5.5)*15;

The end result is as if you had done...

#declare Value = P1+(T1+T/(T2-T1))*(P2-P1) * 15;

which is syntactically legal but not mathematically correct because the P1 term is not multiplied. The parentheses in the original example solves this problem. The end result is as if you had done...

#declare Value = (P1+(T1+T/(T2-T1))*(P2-P1)) * 15;

which is correct.

Returning Values Via Parameters

Sometimes it is necessary to have a macro return more than one value or you may simply prefer to return a value via a parameter as is typical in traditional programming language procedures. POV-Ray macros are capable of returning values this way. The syntax for POV-Ray macro parameters says that the actual parameter may be an IDENTIFIER or an RVALUE. Values may only be returned via a parameter if the parameter is an IDENTIFIER. Parameters that are RVALUES are constant values that cannot return information. An RVALUE is anything that legally may appear to the right of an equals sign in a #declare or #local directive. For example consider the following trivial macro which rotates an object about the x-axis.

#macro Turn_Me(Stuff,Degrees)
  #declare Stuff = object{Stuff rotate x*Degrees}
#end

This attempts to re-declare the identifier Stuff as the rotated version of the object. However the macro might be invoked with Turn_Me(box{0,1},30) which uses a box object as an RVALUE parameter. This will not work because the box is not an identifier. You can however do this

#declare MyObject=box{0,1}
Turn_Me(MyObject,30)

The identifier MyObject now contains the rotated box.

See Identifier Name Collisions for a detailed discussion of how local identifiers, parameters, and global identifiers work when a local identifier has the same name as a previously declared identifier.

While it is obvious that MyObject is an identifier and box{0,1} is not, it should be noted that Turn_Me(object{MyObject},30) will not work because object{MyObject} is considered an object statement and is not a pure identifier. This mistake is more likely to be made with float identifiers versus float expressions. Consider these examples.

#declare Value=5.0;
MyMacro(Value)     //MyMacro can change the value of Value but...
MyMacro(+Value)    //This version and the rest are not lone
MyMacro(Value+0.0) // identifiers. They are float expressions
MyMacro(Value*1.0) // which cannot be changed.

Although all four invocations of MyMacro are passed the value 5.0, only the first may modify the value of the identifier.

Optional Parameters

New in version 3.8 macro parameters can be declared as optional by preceding the parameter identifier with the keyword optional; when calling a macro, you can pass undeclared identifiers to such optional parameters, or even omit them entirely:

MACRO_DEFINITION:
  #macro IDENTIFIER ([PARAM_IDENT] [, PARAM_IDENT]... ) TOKENS... #end
PARAM_IDENT:
  [optional] GENERIC_IDENTIFIER
MACRO_INVOCATION:
  MACRO_IDENTIFIER ( [ACTUAL_PARAM] [, ACTUAL_PARAM]... )
ACTUAL_PARAM:
  [ IDENTIFIER | RVALUE ]

Passing an undeclared identifier to a macro, or omitting a macro parameter entirely, will literally leave the corresponding parameter identifier undeclared as if it wasn't part of the macro's parameter declaration.

Note: If a macro parameter identifier remains undeclared in this manner, any variables of the same name declared outside the macro will become visible instead. To reliably test whether an optional parameter has been supplied, you therefore need to make use of the local pseudo-dictionary, as in the following example.

See also the Array section for more about pseudo-dictionaries.

#macro Foo(P1, optional P2)
#ifndef(local.P2) #local P2 = 0; #end // provide default for P2
...
#end

#declare X=1;
#declare Y=2;
Foo(X,Y)
#undef Y
Foo(X,Y)
Foo(X,)

Note: The optional parameters mechanism can not be used to return a value via a parameter to a previously undeclared identifier.

Macro Caching

Until now, each and every invocation of a macro from a file other than the one it was declared in, inevitably caused the latter file to be opened again for the macro code to be re-read. This process seriously impacts parser performance. To that end in version 3.8 a New cached macro mechanism has been implemented. It keeps a copy of each and every macro's code in memory (except for macros more than 65536 characters in size), greatly increasing the execution speed of macros in such scenarios.