WHENEVER en Genero: una directiva de compilación

El problema: un IF con WHENEVER que no obedece

WHENEVER en Genero parece una instrucción más, y es tentador decidir con un IF qué hacer ante un error:

MAIN
  DEFINE l_seguir BOOLEAN
  DEFINE l_x INTEGER  LET l_seguir = FALSE
IF arg_val(1) == "seguir" THEN
LET l_seguir = TRUE
END IF
IF l_seguir THEN
WHENEVER ANY ERROR CONTINUE
ELSE
WHENEVER ANY ERROR STOP
END IF
DISPLAY "l_seguir = ", l_seguir
LET l_x = 1 / 0
DISPLAY "ha continuado tras el error"
END MAIN

Lanzado con seguir, se esperaría que continuara. No continúa, ni con TRUE ni con FALSE:

l_seguir =      1
Program stopped at 'p1_if.4gl', line number 16.
FORMS statement error number -1202.
An attempt was made to divide by zero.

La causa: lo aplica el compilador, no se ejecuta

WHENEVER no es una instrucción, es una directiva. El manual lo compara con una macro del preprocesador de C: su efecto es local al módulo y define el tratamiento de errores para el resto del módulo, hasta que el compilador encuentra otro WHENEVER de la misma clase o un bloque TRY/CATCH.

«El resto del módulo» se refiere a las líneas que quedan por debajo en el fichero, no a lo que se ejecute después. En el ejemplo, el último WHENEVER escrito antes del 1 / 0 es el STOP de la rama ELSE, y es el que el compilador asigna a esa línea. Qué rama se ejecute da igual: ninguna de las dos «ejecuta» nada.

Tres consecuencias que se pueden comprobar

Una función que nunca se llama cambia a las que tiene debajo

MAIN
  WHENEVER ANY ERROR STOP
  CALL f_despues()
  CALL f_antes()
END MAINFUNCTION f_antes()
DEFINE l_x INTEGER
LET l_x = 1 / 0
DISPLAY "f_antes: ha continuado"
END FUNCTION FUNCTION nunca_se_llama()
WHENEVER ANY ERROR CONTINUE
END FUNCTION
FUNCTION f_despues()
DEFINE l_x INTEGER
LET l_x = 1 / 0
DISPLAY "f_despues: ha continuado"
END FUNCTION
f_despues: ha continuado
Program stopped at 'p2_lexico.4gl', line number 9.
FORMS statement error number -1202.

nunca_se_llama() no se ejecuta nunca, y aun así f_despues, que está escrita debajo, sigue tras el error. f_antes, escrita encima, se para. Lo que cuenta es la posición en el fichero, no el orden de las llamadas.

No pasa de un módulo a otro

El manual lo dice así: un WHENEVER solo define el tratamiento del módulo actual y no afecta a quien llama. Tampoco a quien es llamado. Un módulo sin ningún WHENEVER:

-- m_otro.gl
PUBLIC FUNCTION dividir()
  DEFINE l_x INTEGER
  LET l_x = 1 / 0
  DISPLAY "m_otro: ha continuado"
END FUNCTION

Y un programa que define un manejador y lo llama:

-- p3_modulos.gl
IMPORT FGL m_otro
MAIN
  WHENEVER ANY ERROR CALL manejador
  CALL m_otro.dividir()
  DISPLAY "main: fin"
END MAINFUNCTION manejador()
DISPLAY "manejador de MAIN llamado, status=", status
END FUNCTION
m_otro: ha continuado
main: fin

manejador no se ejecuta: el error ocurre en m_otro, y ahí no hay ningún WHENEVER que lo mande a él.

Sin WHENEVER, la división por cero pasa en silencio

La salida anterior enseña algo más: el 1 / 0 de m_otro no paró nada, y el programa siguió en la línea siguiente. Según el manual, la acción por defecto es STOP, pero no se aplica a los expression errors, como la división por cero o un error de conversión de tipos. Para eso hace falta WHENEVER ANY ERROR o la entrada de FGLPROFILE fglrun.mapAnyErrorToError = true. Con esa entrada activada, el mismo programa se para en la división de m_otro, y sigue sin llamar a manejador.

La solución

Para decidir en ejecución, usa TRY/CATCH. La condición se evalúa dentro del CATCH, que sí es código que se ejecuta:

TRY
  LET l_x = 1 / 0
CATCH
  IF NOT l_seguir THEN
    DISPLAY "error ", status, ": se para"
    EXIT PROGRAM 1
  END IF
  DISPLAY "error ", status, ": se sigue"
END TRY

Para la red de seguridad general, un WHENEVER al principio de cada módulo. Ask Reuben (#16) propone como norma que la primera función de cada .4gl sea una función privada con esa única línea. Así queda definida para todo el fichero, y no depende de que nadie la llame:

IMPORT FGL erroresPRIVATE FUNCTION excepciones()
WHENEVER ANY ERROR CALL errores.manejador
END FUNCTION
PUBLIC FUNCTION dividir()
...
END FUNCTION

En el módulo que tiene el MAIN no se puede: una función antes de MAIN da error de compilación (-6609, A grammatical error has been found at ‘MAIN’). Ahí la directiva va como una de las primeras líneas del propio MAIN.

Dos detalles del manejador:

  • El programa sigue después. Tras ejecutar la función de WHENEVER ... CALL, el programa continúa en la línea siguiente a la del error. Si quieres que pare, tiene que pararlo el manejador.
  • Ponle su propio WHENEVER ANY ERROR STOP dentro. Si el código del manejador falla, así no se vuelve a llamar a sí mismo en bucle.

Conclusión

WHENEVER se lee de arriba abajo en el fichero, no en el orden de ejecución, y cada módulo tiene el suyo. Para decidir según el estado del programa, TRY/CATCH. Para cubrir todo un fichero, un WHENEVER en la parte de arriba.

Fuentes

  • Manual Genero BDL 6.00: WHENEVER directive (alcance de módulo, comparación con el preprocesador de C, acción por defecto, fglrun.mapAnyErrorToError).
  • Ask Reuben #16, When Things Go Wrong (part 3 of 3) – Exception Handling: https://4js.com/ask-reuben/ig-16/

Qué está comprobado y qué no

El alcance de módulo, que no afecta a quien llama, la acción por defecto y fglrun.mapAnyErrorToError están contrastados con el manual de Genero 6.00. El ejemplo del IF y la norma de la función privada vienen de Ask Reuben #16.

Compilado y ejecutado en Genero 5.01.04, en modo texto. Los listados se copiaron tal cual del texto de la entrada; los dos fragmentos (el TRY/CATCH y el módulo con ...) se completaron con un MAIN y un cuerpo mínimos sin cambiar sus líneas:

  • El IF con WHENEVER: se para en la línea 16 con la condición a TRUE y a FALSE.
  • La función que nunca se llama: cambia el comportamiento de la que tiene debajo.
  • Entre módulos: el manejador de MAIN no se ejecuta para un error de m_otro.
  • La división por cero sin WHENEVER: pasa en silencio, y se para con fglrun.mapAnyErrorToError = true.
  • La decisión con TRY/CATCH: sigue o para según la condición.
  • La función privada al principio del módulo: compila con el nombre calificado de otro módulo (errores.manejador), el manejador se llama, y el programa continúa después.
  • Una función antes de MAIN: no compila (-6609).

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Captcha cargando...

Scroll al inicio