6.2.8 Macros Definidas por el Usuario

La versión 3.1 de POV-Ray introdujo las macros con parámetros definidas por el usuario. Esta nueva característica, junto con la posibilidad de declarar variables locales (#local), convierte al lenguaje POV-Ray en un lenguaje de programación completamente funcional. Así, ahora es posible escribir utilidades de generación de escena que antes requerían programación externa.

6.2.8.1 La Directiva #macro

La sintaxis para declarar una macro es la siguiente:

DEFINICION_DE_MACRO:
#macro IDENTIFICADOR ([IDENTIFICADORES_PARAMETROS] [, IDENTIFICADORES_PARAMETROS]... ) 
TOKENS... 
#end

Donde IDENTIFICADOR es el nombre de la macro, e IDENTIFICADORES_PARAMETROS es una lista de cero o más identificadores de parámetros formales, separados por comas y encerrados entre paréntesis. Los paréntesis son obligatorios, incluso si no se especifican parámetros.

Los TOKENS son cualquier número de sentencias, identificadores o marcas de puntuación de POV-Ray, y que constituyen el cuerpo de la macro. El cuerpo de la macro puede contener casi todos los ítem de sintaxis de POV-Ray. Se finaliza con una directiva #end.

Nota: cualquier directriz condicional #if...#end, #while...#end , etc., debe estar completamente cerrada dentro o fuera del cuerpo de la macro, de forma que haya siempre una directiva de cierre #end correspondiente.

Una macro tiene que ser declarada antes de ser invocada. Los nombres de macros son de alcance global y duración permanente. Puedes redefinir una macro, usando otra directriz #macro con el mismo nombre. La definición previa se perderá. Los nombres de macros responden a las directrices #ifdef, #ifndef, y #undef. Véase las secciones "Las directivas #ifdef e #ifndef" y "Destruyendo identificadores con #undef".

6.2.8.2 Invocando las Macros

Una macro se invoca especificando su nombre seguido de una lista de cero o más parámetros encerrados entre paréntesis y separados por comas. El número de parámetros usados debe coincidir con el número de parámetros formales en la definición. Los paréntesis son obligatorios, aunque no existan parámetros. La sintaxis es la siguiente:

INVOCACION_DE_MACRO:
    IDENTIFICADOR_DE_MACRO ( [PARAMETRO_REAL] [, PARAMETRO_REAL]... )
PARAMETRO_REAL:
    IDENTIFICADOR | DVALOR

Un DVALOR es cualquier valor que sea legal a la derecha del signo igual en una declaración #declare o #local. Véase "Declarando identificadores" para más información acerca del DVALOR. Cuando se invoca la macro, se crea una nueva tabla de símbolos locales. Los parámetros pasados se asignan a los identificadores de parámetros formales como variables locales temporales. POV-Ray salta entonces al cuerpo de la macro y continúa analizando hasta encontrar la directriz #end. Entonces se destruyen las variables creadas por los parámetros así como cualquier identificador local expresamente creado en el cuerpo de la macro. Entonces continúa analizando desde el punto en que la macro fue invocada.

Nota: Es posible invocar macros que fueron definidas en otros ficheros. Es bastante usual y, de hecho, así es como funcionan muchos "plugins" (como el popular "Lens Flare"). Sin embargo, debes tener en cuenta que el llamar a una macro que fue declarada en otro archivo diferente, produce más sobrecarga que hacerlo desde el mismo archivo. Ello es debido a que POV-Ray no almacena todas las instrucciones del lenguaje. Al llamar a una macro en otro archivo, es necesario abrir y cerrar el fichero, cada vez que se invoca. Normalmente, la sobrecarga es inapreciable; sin embargo, si estás llamando a una macro cientos de veces, puede causar un retardo considerable. En futuras versiones de POV-Ray se intentará corregir este problema.

He aquí una simple macro que crea un marco de ventana cuando le especificas las dimensiones interior y exterior:

 #macro Hacer_Marco (AnchuraExterior,AlturaExterior,AnchuraInterior,AlturaInterior,Profundidad)
   #local Horiz = (AlturaExterior-AlturaInterior)/2;
   #local Verti = (AnchuraExterior-Anchura_Interior)/2;
   difference
   {
     box{<0,0,0>,<AnchuraExterior,AlturaExterior,Profundidad>}
     box{<Verti,Horiz,-0.1>,<Anchura_Exterior-Verti,AlturaExterior-Horiz,Profundidad+0.1>}
   }
 #end
 Hacer_Marco(8,10,7,9,1) // invocación de la macro

En este ejemplo, la macro tiene cinco parámetros de coma flotante. Los parámetros reales (los valores 8, 10, 7, 9 y 1), son asignados a los cinco identificadores de la lista de parámetros formales de la directiva #macro. Es como si hubieras usado las siguientes cinco líneas de código:

 #local AnchuraExterior = 8;
 #local AlturaInterior = 10;
 #local AnchuraInterior = 7;
 #local AlturaInterior = 9;
 #local Profundidad = 1;

Estos cinco identificadores se almacenan en la misma tabla de símbolos que cualquier otro identificador local, como Horiz o Verti en el ejemplo. Los parámetros y variables locales son destruidos al alcanzar la sentencia #end. Mira en "Conflictos con Nombres de Identificadores" para una discusión detallada de cómo funcionan los identificadores locales, parámetros e identificadores globales, cuando un identificador tiene el mismo nombre que otro declarado previamente.

6.2.8.3 Las macros de POV-Ray, ¿son funciones o son macros?

Las macros de POV-Ray son una extraña mezcla de macros y funciones. En los lenguajes de programación tradicionales, una macro trabaja siempre por sustitución de token. El cuerpo de la rutina se inserta en el punto de invocación, simplemente copiando los tokens y analizándolos como si hubieran sido escritos en ese lugar. Este tipo de sustitución se llama normalmente macro substitución, ya que es así como funcionan las macros. En este aspecto, las macros de POV-Ray son como las macros tradicionales al usar macrosubstitución para el cuerpo de la macro. Sin embargo, las macros tradicionales usan esta misma técnica para los parámetros, mientras que POV-Ray no.

Imagina que tienes una macro en el lenguaje de programación C: Typical_Cmac(Param) y la invocas como: Typical_Cmac(else A=B). En cualquier sitio que Param aparezca en el cuerpo de la macro, los cuatro tokens else, A, =, y B se sustituirán en el código del programa usando la macro sustitución. No se comprobará el tipo, ya que cualquier cosa es legal. La capacidad de pasar un grupo arbitrario de tokens mediante un parámetro de macro es una potente característica de las macros tradicionales (aunque a veces se abusa de ella).

Tras una cuidadosa deliberación, nos decidimos en contra de este tipo de parámetros para nuestras macros. La razón es que POV-Ray usa las comas en su sintaxis más a menudo que la mayoría de lenguajes. Supón que creas una macro diseñada para operar sobre un vector y dos valores de coma flotante. Podría ser definida como MiMacro(V,F1,F2). Si permites cadenas arbitrarias de tokens e invocas la macro como MiMacro(<1,2,3>,4,5), es imposible entonces decir si hablamos de un vector y dos valores de coma flotante, o de cinco parámetros, siendo el conjunto <1 el primer parámetro. Si diseñamos la macro para aceptar cinco parámetros, entonces no podremos invocar la macro como MiMacro(MiMatriz,4,5).

Los parámetros de funciones en los lenguajes de programación tradicionales, no usan la sustitución de tokens para pasar valores, sino que crean variables locales temporales para guardar los parámetros, que son valores constantes o referencias a identificadores (punteros a variables). Las macros de POV-Ray usan este sistema al estilo de las funciones para pasar parámetros a sus macros. En nuestro ejemplo MiMacro(<1,2,3>,4,5), POV-Ray lee el carácter < y lo interpreta como el principio de una matriz. Analiza la expresión de matriz entera y la asigna al primer parámetro, tal y como si hubieras usado la sentencia #local V=<1,2,3>;.

Aunque hemos dicho que los parámetros en POV-Ray se parecen más a los parámetros de las funciones tradicionales que a los de las macros, existe una diferencia. Muchos lenguajes requieren que declares el tipo de cada parámetro en la definición antes de usarlos, pero POV-Ray no lo requiere. Esto significa que si pasas un parámetro de tipo incorrecto a una macro de POV-Ray, no generara un error hasta que referencias el identificador en el cuerpo de la macro. No se comprueba el tipo cuando se pasa el parámetro. Así, sólo en este pequeño detalle, los parámetros de POV-Ray se parecen a los de las macros.

6.2.8.4 Devolviendo un Valor como en una Función

Las macros de POV-Ray tiene gran variedad de usos. Como la mayoría de las macros, proveen la facilidad de insertar código arbitrario en un archivo de escena, de forma parametrizada. Sin embargo, las macros de POV-Ray también pueden usarse como las funciones o los procedimientos de los lenguajes de programación tradicionales. Ello es especialmente útil, ya que POV-Ray no posee funciones o procedimientos definidos por el usuario. Las macros están hechas también para suplir esta carencia.

Cuando el cuerpo de una macro consiste en sentencias que crean un ítem entero, como por ejemplo un objeto o textura, la macro actúa como una función que devuelve un valor. La macro de ejemplo Hacer_Marco de la sección "Invocando las Macros" ilustra este tipo de macros que devuelven un valor que es un objeto. He aquí algunos ejemplo de cómo debería invocarse:

 union {  // hacer una unión de dos objetos
   object{ Hacer_Marco(8,10,7,9,1) translate  20*x}
   object{ Hacer_Macro(8,10,7,9,1) translate -20*x}
 }
 #declare Marco_Grande = object{ Hacer_Marco(8,10,7,9,1)}
 #declare Marco_Pequeño = object{ Hacer_Marco(5,4,4,3,0.5)}

Como no existe ningún tipo de comprobación del tipo de los parámetros, y dado que la sintaxis de expresiones de coma flotante, vectores y colores es idéntica, puedes crear macros que trabajen con los tres tipos. Mira la escena de ejemplo MACRO3.POV, que incluye esta macro para interpolar valores:

 // Definición de Macro.  Los parámetros son:
 //   T:  Tiempo medio
 //   T1: Tiempo inicial
 //   T2: Tiempo final
 //   P1: Posición Inicial (coma flotante, vector o color)
 //   P2: Posición Final (coma flotante, vector o color) 
 //   El Resultado es un valor proporcional entre P1 y P2,
 //   tal como T lo es entre T1 y T2.
 #macro Interpolar(T,T1,T2,P1,P2)
   (P1+(T1+T/(T2-T1))*(P2-P1))
 #end

Debes invocarla con P1 y P2 como valores de coma flotante, matrices o colores, de la siguiente forma:

  sphere{
    Interpolar(I,0,15,<2,3,4>,<9,8,7>),  // la posición del centro es un vector
    Interpolar(I,0,15,3.0,5.5)           // el radio es un valor de coma flotante
    pigment{
      color Interpolar(I,0,15,rgb<1,1,0>,rgb<0,1,1>)
    }
  }

Tal como el valor de coma flotante I va variando de 0 a 15, la posición, radio y color de la esfera varían en consonancia.

Hay un peligro al usar las macros como funciones. En las funciones de los lenguajes de programación tradicionales, el resultado a devolver se asigna realmente a una variable temporal y el código que la invoca lo trata como si fuera una variable de un tipo dado. Sin embargo, la macrosubstitución puede derivar a veces en una sintaxis inválida o no deseada. Observa que la definición de la macro Interpolar del ejemplo anterior tiene un par de paréntesis exteriores englobando el resultado. Sin estos paréntesis, el resultado de

 #declare Valor = Interpolar(I,0,15,3.0,5.5)*15;

sería como si hubieras hecho...

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

lo cual es sintácticamente legal, pero no matemáticamente correcto, ya que el término P1 no se multiplica. Los paréntesis en el ejemplo original solventan este problema. El resultado final es como si hicieras...

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

que es lo correcto.

6.2.8.5 Devolviendo Valores mediante los Parámetros

A veces es necesario que la macro devuelva más de un valor, o puede que prefieras devolver un valor a través de un parámetro, como suele hacerse en los procedimientos de los lenguajes de programación tradicionales. Las macros de POV-Ray son capaces de devolver valores de esta forma. La sintaxis de los parámetros de las macros de POV-Ray permite que el parámetro real sea un IDENTIFICADOR o un DVALOR (R_VALUE). Los valores sólo pueden ser devueltos a través de los parámetros si éste es un IDENTIFICADOR. Los parámetros con DVALORES son valores constantes que no pueden devolver información. Un DVALOR es cualquier cosa que pueda aparecer legalmente a la derecha de un signo igual en una directriz #declare o #local. Por ejemplo, considera la siguiente macro trivial, que rota un objeto alrededor del eje x.

 #macro Rotar(Cosa,Grados)
   #declare Cosa = object{Cosa rotate x*Grados}
 #end

Este código intentaría redeclarar el identificador Cosa como la versión rotada del objeto. Sin embargo, la macro podría ser invocada con Rotar(box{0,1},30) que usa un objeto box como parámetro del DVALOR. No funcionaría, ya que el objeto box no es un identificador. Podrías, sin embargo, hacer esto:

   #declare MiObjeto=box{0,1}
   Rotar(MiObjeto,30)

El identificador MiObjeto contendrá entonces la caja rotada.

Mira la sección "Conflictos con Nombres de Identificadores", donde hay una discusión detallada sobre cómo funcionan los parámetros e identificadores locales y globales cuando un identificador local tiene el mismo nombre que un identificador declarado previamente.

Aunque es obvio que MiObjeto es un identificador y que box{0,1} no lo es, debería observarse que Rotar(object{MiObjeto},30) no funcionaría, ya que object{MiObjeto} se considera como una sentencia de objeto, y no es un identificador puro. Este error es más probable que te suceda entre identificadores y expresiones de coma flotante. Considera los siguientes ejemplos:

  #declare Valor=5.0;
  MiMacro(Valor)     //MiMacro puede cambiar el contenido de Valor, pero...
  MiMacro(+Valor)    //este ejemplo y los siguientes, no son puros
  MiMacro(Valor+0.0) // identificadores. Son expresiones,
  MiMacro(Valor*1.0) // que no pueden ser modificadas.

Aunque las cuatro invocaciones de MiMacro han pasado el valor 5.0 en Valor, sólo la primera de ellas puede modificar el valor del identificador.