Saltar al contenido principal

Errores y excepciones

Cuando algo falla con el programa en marcha (un índice que no existe, una división por cero, un fichero que no está), la VM lanza una excepción: un error con un nombre, como ERR_INDEX_OUT_OF_RANGE, y un mensaje. Si nadie la captura, el programa se para. Con try y catch la capturas y decides qué hacer.

No todos los fallos son excepciones:

  • Algunas funciones de los módulos no lanzan nada: devuelven un valor de error, y el programa sigue (ver Valores de error).
  • Un error al compilar no llega a ejecutarse: el compilador se para antes, como cuenta Tu primer programa.

try, catch y las excepciones funcionan igual en Windows y en todas las placas. Lo que cambia es dónde ves un error que nadie captura.

Un error sin capturar​

Este programa pide el nivel 3 de una lista que solo tiene el 0, el 1 y el 2:

var niveles = ["Bosque", "Cueva", "Castillo"]

func nombreNivel(n)
return niveles[n]
end

println(nombreNivel(1))
println(nombreNivel(3))
println("Esta línea no se ejecuta")

Escribe Cueva y se para en la línea 4. La VM escribe en su salida de errores:

====================================================================
UNHANDLED EXCEPTION
====================================================================
Type : Unhandled exception.
Exception : ERR_INDEX_OUT_OF_RANGE
Message : List index out of range.
Location : line 4 in nombreNivel()

---------------------- EXECUTION TRACE ----------------------
1. line 4 in nombreNivel()
2. line 8 in <main>()
------------------------------=====----------------------------

[ JARU VM terminated execution due to a critical error ]
====================================================================
  • Exception es el nombre del error: una de las constantes ERR_*.
  • Message dice qué ha pasado, en inglés.
  • Location y EXECUTION TRACE dicen dónde: la línea y la función, desde la llamada más interna hasta <main>(), que es el código que está fuera de las funciones.
  • La línea es la de su fichero, pero no dice qué fichero es. En un programa de varios ficheros, búscala por la función.
  • Las líneas salen si la casilla Line numbers está encendida, como viene de serie (ver La interfaz). Apagada, salen como line -1.

En Windows, con Run, el error sale en la consola del programa al acabar, y el Output lo copia en rojo. Lo cuenta paso a paso Tu primer programa.

En una placa, el error sale por el puerto serie: lo ves en el Output con Open Serial. Después, la VM vuelve a empezar el programa desde el principio, así que el error se repite hasta que lo corrijas (ver Tu programa en una placa).

try y catch​

try
// el código que puede fallar
catch (var e)
// lo que haces si falla: e es el error
end
  • Si nada falla dentro de try, el bloque catch no se ejecuta.
  • Si algo falla, el resto del bloque try se salta: el programa pasa al catch, con el error en la variable. Después sigue detrás del end.
  • catch lleva siempre los paréntesis y var, con una sola variable. catch (e), sin var, no compila.
  • Cada try tiene un catch, y solo uno. No hay finally.
  • Las excepciones las lanza la VM. Tu programa no puede lanzar las suyas: JARU no tiene throw.
var precios = [10, 20, 30]
var i = 5

try
var precio = precios[i]
println("Precio: ", precio) // no se ejecuta
catch (var e)
println("No hay precio ", i, ": ", e.name)
end
println("El programa sigue")
No hay precio 5: ERR_INDEX_OUT_OF_RANGE
El programa sigue

El error: e​

La variable del catch tiene tres propiedades:

PropiedadQué esEjemplo
e.nameEl nombre del error, en una cadena."ERR_INDEX_OUT_OF_RANGE"
e.messageQué ha pasado, en inglés."List index out of range."
e.tracebackUna lista de cadenas, una por llamada, de la más interna a <main>().["line 4 in nombreNivel()", "line 8 in <main>()"]
  • println(e) escribe el nombre y el mensaje: ERR_INDEX_OUT_OF_RANGE: List index out of range.
  • type(e) vale tError (ver type).
  • Cualquier otra propiedad lanza ERR_ATTRIBUTE_NOT_FOUND.
  • La variable solo existe dentro del catch. Si la usas detrás del end, el programa se para con ERR_NAME_NOT_FOUND.

Para hacer algo distinto según el error, compara e.name con su nombre, entre comillas. Las constantes ERR_* son números, y el error no lleva el suyo: e.name == ERR_KEY_NOT_FOUND es siempre falso.

var almacen = {"manzanas": 4}

func quitar(fruta)
try
almacen[fruta] = almacen[fruta] - 1
println("Quedan ", almacen[fruta], " ", fruta)
catch (var e)
if (e.name == "ERR_KEY_NOT_FOUND") then
println("No hay ", fruta)
else
println("Otro error: ", e)
end
end
end

quitar("manzanas")
quitar("peras")
Quedan 3 manzanas
No hay peras

Errores dentro de funciones​

try también captura lo que falla en las funciones a las que llama, a cualquier profundidad. Al saltar al catch, esas llamadas se abandonan.

func media(lista)
var suma = 0
foreach (var n in lista)
suma = suma + n
end
return suma div len(lista)
end

try
println(media([4, 6, 8]))
println(media([]))
catch (var e)
println(e)
foreach (var paso in e.traceback)
println(" ", paso)
end
end
6
ERR_DIVISION_BY_ZERO: Division by zero
line 6 in media()
line 11 in <main>()

Dentro de un try puedes salir con return, break o continue, como de cualquier otro bloque.

Un try dentro de otro​

Lo que falla dentro de un catch lo captura el try de fuera, si lo hay:

try
try
var ajustes = {"vidas": 3}
println(ajustes["nivel"])
catch (var e)
println("Primer error: ", e.name)
println(10 div 0)
end
catch (var e)
println("Segundo error: ", e.name)
end
Primer error: ERR_KEY_NOT_FOUND
Segundo error: ERR_DIVISION_BY_ZERO

Puede haber 16 try abiertos a la vez, como mucho, contando los de las funciones que están en marcha. Al abrir el 17.º, el programa se para con el error Too many nested try-catch blocks., y ese no se puede capturar. Le pasa sobre todo a una función recursiva con un try dentro: no llega a 17 llamadas.

Lo que try no captura​

  • Los valores de error que devuelven algunas funciones: no son excepciones (ver Valores de error).
  • exit, que termina el programa aunque esté dentro de un try (ver exit).
  • Los fallos de la propia VM: el 17.º try abierto a la vez, y que se llene la pila de la VM.
Una recursión muy profunda cierra la VM sin avisar

La VM guarda las variables de las llamadas en marcha en una pila de tamaño fijo. En Windows, si se llena, la VM se cierra sin ningún mensaje, y try no lo captura; en una placa no está probado. Pasa a unas 80 llamadas anidadas de una función con un parámetro, y antes cuantas más variables tenga la función: a unas 35 con cinco. Una lista o un mapa literales muy largos también la llenan (ver Listas, mapas y arrays). Si una función se llama a sí misma muchas veces, escríbela con un bucle.

Las constantes ERR_*​

Cada error tiene un nombre y un número. El nombre es el que dan e.name y el mensaje de un error sin capturar. El número es el valor de su constante, y es el que se ve en un valor de error: Error 9 es ERR_INVALID_ARG_NUMBER.

ConstanteValorQué quiere decirCuándo sale
ERR_UNKNOWN1Un error sin clasificar.Al llamar a un método de un valor de error.
ERR_OUT_OF_MEMORY2No queda memoria.Al cargar un bitmap o leer un fichero entero, y al crear un GridMap, un sprite o una tira de LED, si no caben. En Windows, también cuando GPIO se queda sin canales PWM o sin sitio para más interrupciones.
ERR_RECURSION_DEPTH_EXCEEDED3Demasiadas llamadas anidadas.No sale en la 0.9.7: si se llena la pila, la VM se cierra (ver Lo que try no captura).
ERR_ASSERTION_FAILED4Una comprobación que no se cumple.No sale en la 0.9.7.
ERR_NAME_NOT_FOUND5Un nombre que no existe.Una variable o una función mal escrita, sin declarar o que todavía no se ha declarado: Undefined variable 'x'.
ERR_ATTRIBUTE_NOT_FOUND6Un método o una propiedad que no existe.Un método que el valor no tiene, como texto.noexiste(); una propiedad que el objeto no tiene, o privada y leída desde fuera de su clase; e.codigo en el error de un catch.
ERR_TYPE_MISMATCH7Un valor de un tipo que no vale ahí.Sumar una cadena y un número; float("3.5"); llamar a algo que no es una función; un argumento de otro tipo, en casi todas las funciones.
ERR_INVALID_VALUE8El tipo vale, pero el valor no.JSON.parse con un texto que no es JSON; Input.map con una tecla que no existe; un tile fuera de 0-255 en un GridMap; una tira de más de 1024 LED.
ERR_INVALID_ARG_NUMBER9Argumentos de más o de menos.Al llamar a una función tuya, a un método o a una función de un módulo con otro número de argumentos. Con casi todas las de la librería estándar, el programa ni siquiera compila.
ERR_EMPTY_LIST10La lista está vacía.lista.pop() con la lista vacía.
ERR_INDEX_OUT_OF_RANGE11Una posición que no existe.Un índice fuera de una lista, una cadena o un array, también los negativos; insert o pop de una lista con un índice que no está; una celda fuera de un GridMap; un LED fuera de su tira; un byte fuera de su Bytes.
ERR_KEY_NOT_FOUND12La clave no está en el mapa.mapa["clave"] o mapa.pop("clave") con una clave que el mapa no tiene.
ERR_INVALID_OPERATION13Algo que no se puede hacer así.Un método sin paréntesis (texto.size); asignar a una propiedad de solo lectura (Display.dt = 1); un operador que el objeto no tiene, como sumar un número a una instancia de una clase sin operator +; usar una tira de LED ya cerrada.
ERR_DIVISION_BY_ZERO14División por cero./, div o mod con un 0 a la derecha, también con decimales: 1.0 / 0.
ERR_NOT_IMPLEMENTED15Algo sin hacer.No sale en la 0.9.7.
ERR_MODULE_NOT_FOUND16Un módulo sin abrir.Usar un módulo sin su use, o antes de que se ejecute: Module 'Math' not found or not imported. (ver use).
ERR_CIRCULAR_IMPORT17Un import circular.No sale: un import circular no compila (ver import).
ERR_FILE_NOT_FOUND18El fichero no está.File.open en modo "r", File.delete, File.rename o File.copy con un fichero que no existe; Bitmap.load y GridMap.load con uno que no pueden abrir; File.setUnit("sd:") en una placa sin tarjeta SD.
ERR_FILE_EXISTS19El fichero ya existe.No sale en la 0.9.7.
ERR_PERMISSION_DENIED20Sin permiso.Llamar a un método privado (def private) desde fuera de su clase.
ERR_IS_A_DIRECTORY21Es una carpeta, no un fichero.No sale en la 0.9.7.
ERR_NOT_A_DIRECTORY22Es un fichero, no una carpeta.No sale en la 0.9.7.
ERR_FILE_READ_FAIL23No se ha podido leer.Leer de un fichero cerrado, o un fallo al leerlo.
ERR_FILE_WRITE_FAIL24No se ha podido escribir.Escribir en un fichero abierto con "r" o ya cerrado; File.mkDir y File.rmDir cuando no pueden; Bitmap.writeHeader y Bitmap.writeLine.
ERR_INVALID_FORMAT25El fichero no tiene el formato que toca.GridMap.load con un fichero que no es un .gmap de JARU, que es de otra versión o que está cortado.
ERR_NETWORK_UNAVAILABLE26No hay red.No sale en la 0.9.7.
ERR_CONNECTION_REFUSED27El otro lado rechaza la conexión.No sale en la 0.9.7.
ERR_CONNECTION_RESET28El otro lado ha cortado la conexión.No sale en la 0.9.7.
ERR_TIME_OUT29Se ha acabado el tiempo de espera.No sale en la 0.9.7.

Las que dicen «No sale en la 0.9.7» existen, pero la VM de esta versión no las usa nunca. Los fallos de red tampoco son excepciones: Http y MQTT los cuentan con sus funciones, como Http.status(), Http.error() y MQTT.isConnect() (ver Http y MQTT).

Valores de error​

Algunas funciones de los módulos no lanzan una excepción cuando fallan: devuelven un valor de error, y el programa sigue como si nada. Pasa sobre todo cuando se las llama mal, con argumentos de más, de menos o de otro tipo. Y en algunos fallos que cuenta la página de cada módulo o tipo, como texto.toFloat() con un texto que no es un número (ver Cadenas).

use Power

var r = Power.wakeOnGPIO() // le falta el pin
println(r) // Error 9
println("El programa sigue")

Un valor de error se imprime como Error y un número: el valor de la constante del error. Error 9 es ERR_INVALID_ARG_NUMBER.

En una placa, algunas de estas funciones escriben además un aviso por el puerto serie: el mensaje (Wrong argument count in 'Power.wakeOnGPIO()'.), Traceback : y la pila de la VM. En Windows no avisan.

Un valor de error se comporta así:

  • try no lo ve: no es una excepción.
  • En un if cuenta como verdadero.
  • No es igual a su constante: r == ERR_INVALID_ARG_NUMBER da false. Dos valores de error con el mismo número sí son iguales entre sí.
  • Llamar a un método suyo lanza ERR_UNKNOWN, y sumarlo a una cadena, ERR_TYPE_MISMATCH.
  • type() da tError, como el error de un catch.

Saber si un valor es de error​

Con type(): un valor de error da tError.

var texto = "abc"
var numero = texto.toFloat()
if (type(numero) == tError) then
println("No es un número: ", numero) // No es un número: Error 7
end
println(type(2.5) == tError) // false

Mejor aún es no llegar a él: llama a cada función con los argumentos que pide su página. Y si una llamada no hace lo que esperas, imprime lo que devuelve: si sale Error y un número, búscalo en la tabla de las constantes.

Al asignar una propiedad​

Las propiedades de los módulos y de los objetos de la VM (un sprite, una escena…) comprueban el valor que les das. Con uno que no vale, depende de la propiedad:

  • Unas lanzan una excepción, que try captura: Display.fps = "rápido" lanza ERR_TYPE_MISMATCH.
  • Otras no hacen nada, sin avisar: la propiedad se queda como estaba. Display.showBG = "sí" deja showBG en false.
  • Y otras escriben un aviso en la salida de errores y tampoco cambian nada: sprite.x = "uno" escribe [ ERROR ] In 'x', y x se queda como estaba.
use Display

try
Display.showBG = "sí"
println("showBG sigue en ", Display.showBG)
Display.fps = "rápido"
println("Esta línea no se ejecuta")
catch (var e)
println(e)
end
showBG sigue en false
ERR_TYPE_MISMATCH: Display.fps must be a number.

Las propiedades de tus clases y los campos de tus structs no comprueban nada: guardan cualquier valor.

exit​

exit termina el programa en el acto, desde cualquier sitio: dentro de una función, de un bucle o de un try. Lleva detrás un número entero, el código de salida, con paréntesis o sin ellos: exit 1 o exit(1). Sin número no compila.

func dividir(a, b)
if (b == 0) then
println("No se puede dividir ", a, " entre 0")
exit 1
end
return a div b
end

println(dividir(10, 2))
println(dividir(7, 0))
println("Esta línea no se ejecuta")

Escribe:

5
No se puede dividir 7 entre 0

Y en la salida de errores, una línea por cada llamada en marcha, de la más interna a <main>():

> Exit :[line 4] in dividir() with code 1 <
> Exit :[line 10] in <main>() with code 1 <
  • try no lo captura.
  • El código solo sale en esas líneas: no cambia nada más. Tiene que ser un entero: con otra cosa, la línea dice un número sin sentido.
  • En Windows, con Run, esas líneas salen al final de la consola, y el Output las copia en rojo, como los errores.
  • En una placa, exit no deja el programa parado: la VM lo vuelve a empezar, como cuando acaba solo (ver Tu programa en una placa). Para que se quede quieto, termínalo con un bucle que no acabe, como while (true) pause(1000) end.
  • exit es una instrucción, no una función: no se puede usar dentro de una expresión.

vmerror()​

vmerror() es una función de la librería estándar que debería decir el código del último error.

vmerror() devuelve siempre 0

En la 0.9.7, vmerror() devuelve 0 aunque haya habido errores, así que no sirve para saber si algo ha fallado. Para las excepciones, usa try y catch; para los valores de error, mira lo que devuelve cada función.

Todos los problemas conocidos de JARU, juntos, están en Problemas conocidos.