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 ]
====================================================================
Exceptiones el nombre del error: una de las constantesERR_*.Messagedice qué ha pasado, en inglés.LocationyEXECUTION TRACEdicen 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 bloquecatchno se ejecuta. - Si algo falla, el resto del bloque
tryse salta: el programa pasa alcatch, con el error en la variable. Después sigue detrás delend. catchlleva siempre los paréntesis yvar, con una sola variable.catch (e), sinvar, no compila.- Cada
trytiene uncatch, y solo uno. No hayfinally. - 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:
| Propiedad | Qué es | Ejemplo |
|---|---|---|
e.name | El nombre del error, en una cadena. | "ERR_INDEX_OUT_OF_RANGE" |
e.message | Qué ha pasado, en inglés. | "List index out of range." |
e.traceback | Una 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)valetError(ver type).- Cualquier otra propiedad lanza
ERR_ATTRIBUTE_NOT_FOUND. - La variable solo existe dentro del
catch. Si la usas detrás delend, el programa se para conERR_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 untry(ver exit).- Los fallos de la propia VM: el 17.º
tryabierto a la vez, y que se llene la pila de la VM.
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.
| Constante | Valor | Qué quiere decir | Cuándo sale |
|---|---|---|---|
ERR_UNKNOWN | 1 | Un error sin clasificar. | Al llamar a un método de un valor de error. |
ERR_OUT_OF_MEMORY | 2 | No 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_EXCEEDED | 3 | Demasiadas 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_FAILED | 4 | Una comprobación que no se cumple. | No sale en la 0.9.7. |
ERR_NAME_NOT_FOUND | 5 | Un 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_FOUND | 6 | Un 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_MISMATCH | 7 | Un 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_VALUE | 8 | El 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_NUMBER | 9 | Argumentos 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_LIST | 10 | La lista está vacía. | lista.pop() con la lista vacía. |
ERR_INDEX_OUT_OF_RANGE | 11 | Una 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_FOUND | 12 | La clave no está en el mapa. | mapa["clave"] o mapa.pop("clave") con una clave que el mapa no tiene. |
ERR_INVALID_OPERATION | 13 | Algo 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_ZERO | 14 | División por cero. | /, div o mod con un 0 a la derecha, también con decimales: 1.0 / 0. |
ERR_NOT_IMPLEMENTED | 15 | Algo sin hacer. | No sale en la 0.9.7. |
ERR_MODULE_NOT_FOUND | 16 | Un 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_IMPORT | 17 | Un import circular. | No sale: un import circular no compila (ver import). |
ERR_FILE_NOT_FOUND | 18 | El 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_EXISTS | 19 | El fichero ya existe. | No sale en la 0.9.7. |
ERR_PERMISSION_DENIED | 20 | Sin permiso. | Llamar a un método privado (def private) desde fuera de su clase. |
ERR_IS_A_DIRECTORY | 21 | Es una carpeta, no un fichero. | No sale en la 0.9.7. |
ERR_NOT_A_DIRECTORY | 22 | Es un fichero, no una carpeta. | No sale en la 0.9.7. |
ERR_FILE_READ_FAIL | 23 | No se ha podido leer. | Leer de un fichero cerrado, o un fallo al leerlo. |
ERR_FILE_WRITE_FAIL | 24 | No 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_FORMAT | 25 | El 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_UNAVAILABLE | 26 | No hay red. | No sale en la 0.9.7. |
ERR_CONNECTION_REFUSED | 27 | El otro lado rechaza la conexión. | No sale en la 0.9.7. |
ERR_CONNECTION_RESET | 28 | El otro lado ha cortado la conexión. | No sale en la 0.9.7. |
ERR_TIME_OUT | 29 | Se 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í:
tryno lo ve: no es una excepción.- En un
ifcuenta como verdadero. - No es igual a su constante:
r == ERR_INVALID_ARG_NUMBERdafalse. 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()datError, como el error de uncatch.
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
trycaptura:Display.fps = "rápido"lanzaERR_TYPE_MISMATCH. - Otras no hacen nada, sin avisar: la propiedad se queda como estaba.
Display.showBG = "sí"dejashowBGenfalse. - Y otras escriben un aviso en la salida de errores y tampoco cambian nada:
sprite.x = "uno"escribe[ ERROR ] In 'x',yxse 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 <
tryno 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,
exitno 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, comowhile (true) pause(1000) end. exites 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 0En 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.