Errors and exceptions
When something fails while the program is running (an index that does not exist, a division by zero, a missing file), the VM throws an exception: an error with a name, such as ERR_INDEX_OUT_OF_RANGE, and a message. If nobody catches it, the program stops. With try and catch you catch it and decide what to do.
Not every failure is an exception:
- Some module functions throw nothing: they return an error value, and the program goes on (see Error values).
- A compile error never gets to run: the compiler stops first, as Your first program shows.
try, catch and exceptions work the same on Windows and on every board. What changes is where you see an error that nobody catches.
An uncaught error
This program asks for level 3 of a list that only has 0, 1 and 2:
var levels = ["Forest", "Cave", "Castle"]
func levelName(n)
return levels[n]
end
println(levelName(1))
println(levelName(3))
println("This line does not run")
It prints Cave and stops at line 4. The VM writes to its error output:
====================================================================
UNHANDLED EXCEPTION
====================================================================
Type : Unhandled exception.
Exception : ERR_INDEX_OUT_OF_RANGE
Message : List index out of range.
Location : line 4 in levelName()
---------------------- EXECUTION TRACE ----------------------
1. line 4 in levelName()
2. line 8 in <main>()
------------------------------=====----------------------------
[ JARU VM terminated execution due to a critical error ]
====================================================================
Exceptionis the name of the error: one of theERR_*constants.Messagesays what happened.LocationandEXECUTION TRACEsay where: the line and the function, from the innermost call up to<main>(), which is the code outside any function.- The line is the line in its own file, but it does not say which file. In a program split into several files, look for it by the function.
- The lines are there when the Line numbers box is on, as it comes by default (see The interface). With it off, they show as
line -1.
On Windows, with Run, the error comes out in the program's console when it ends, and the Output copies it in red. Your first program goes through it step by step.
On a board, the error comes out through the serial port: you see it in the Output with Open Serial. Then the VM starts the program again from the beginning, so the error repeats until you fix it (see Your program on a board).
try and catch
try
// the code that may fail
catch (var e)
// what you do if it fails: e is the error
end
- If nothing fails inside
try, thecatchblock does not run. - If something fails, the rest of the
tryblock is skipped: the program goes tocatch, with the error in the variable. Then it carries on after theend. catchalways takes the parentheses andvar, with a single variable.catch (e), withoutvar, does not compile.- Each
tryhas onecatch, and only one. There is nofinally. - Exceptions are thrown by the VM. Your program cannot throw its own: JARU has no
throw.
var prices = [10, 20, 30]
var i = 5
try
var price = prices[i]
println("Price: ", price) // does not run
catch (var e)
println("No price ", i, ": ", e.name)
end
println("The program goes on")
No price 5: ERR_INDEX_OUT_OF_RANGE
The program goes on
The error: e
The catch variable has three properties:
| Property | What it is | Example |
|---|---|---|
e.name | The name of the error, as a string. | "ERR_INDEX_OUT_OF_RANGE" |
e.message | What happened. | "List index out of range." |
e.traceback | A list of strings, one per call, from the innermost to <main>(). | ["line 4 in levelName()", "line 8 in <main>()"] |
println(e)writes the name and the message:ERR_INDEX_OUT_OF_RANGE: List index out of range.type(e)istError(see type).- Any other property throws
ERR_ATTRIBUTE_NOT_FOUND. - The variable only exists inside the
catch. If you use it after theend, the program stops withERR_NAME_NOT_FOUND.
To do something different depending on the error, compare e.name with its name, in quotes. The ERR_* constants are numbers, and the error does not carry its number: e.name == ERR_KEY_NOT_FOUND is always false.
var store = {"apples": 4}
func takeOne(fruit)
try
store[fruit] = store[fruit] - 1
println("There are ", store[fruit], " ", fruit, " left")
catch (var e)
if (e.name == "ERR_KEY_NOT_FOUND") then
println("No ", fruit)
else
println("Another error: ", e)
end
end
end
takeOne("apples")
takeOne("pears")
There are 3 apples left
No pears
Errors inside functions
try also catches what fails in the functions it calls, at any depth. When it jumps to catch, those calls are abandoned.
func average(list)
var total = 0
foreach (var n in list)
total = total + n
end
return total div len(list)
end
try
println(average([4, 6, 8]))
println(average([]))
catch (var e)
println(e)
foreach (var step in e.traceback)
println(" ", step)
end
end
6
ERR_DIVISION_BY_ZERO: Division by zero
line 6 in average()
line 11 in <main>()
Inside a try you can leave with return, break or continue, as from any other block.
A try inside another
What fails inside a catch is caught by the outer try, if there is one:
try
try
var settings = {"lives": 3}
println(settings["level"])
catch (var e)
println("First error: ", e.name)
println(10 div 0)
end
catch (var e)
println("Second error: ", e.name)
end
First error: ERR_KEY_NOT_FOUND
Second error: ERR_DIVISION_BY_ZERO
There can be at most 16 try blocks open at the same time, counting those of the functions that are running. When the 17th opens, the program stops with the error Too many nested try-catch blocks., and that one cannot be caught. It mostly happens to a recursive function with a try inside: it cannot get to 17 calls.
What try does not catch
- The error values that some functions return: they are not exceptions (see Error values).
exit, which ends the program even inside atry(see exit).- Failures of the VM itself: the 17th
tryopen at the same time, and the VM's stack filling up.
The VM keeps the variables of the running calls in a fixed-size stack. On Windows, if it fills up, the VM closes without any message, and try does not catch it; on a board it has not been tested. It happens at about 80 nested calls of a function with one parameter, and sooner the more variables the function has: at about 35 with five. A very long literal list or map fills it up too (see Lists, maps and arrays). If a function calls itself many times, write it with a loop.
The ERR_* constants
Each error has a name and a number. The name is what e.name and the message of an uncaught error give. The number is the value of its constant, and it is what you see in an error value: Error 9 is ERR_INVALID_ARG_NUMBER.
| Constant | Value | What it means | When it happens |
|---|---|---|---|
ERR_UNKNOWN | 1 | An unclassified error. | When you call a method of an error value. |
ERR_OUT_OF_MEMORY | 2 | No memory left. | When loading a bitmap or reading a whole file, and when creating a GridMap, a sprite or an LED strip, if they do not fit. On Windows, also when GPIO runs out of PWM channels or of room for more interrupts. |
ERR_RECURSION_DEPTH_EXCEEDED | 3 | Too many nested calls. | Not used in 0.9.7: if the stack fills up, the VM closes (see What try does not catch). |
ERR_ASSERTION_FAILED | 4 | A check that does not hold. | Not used in 0.9.7. |
ERR_NAME_NOT_FOUND | 5 | A name that does not exist. | A variable or a function misspelled, not declared or not declared yet: Undefined variable 'x'. |
ERR_ATTRIBUTE_NOT_FOUND | 6 | A method or a property that does not exist. | A method the value does not have, such as text.nothere(); a property the object does not have, or a private one read from outside its class; e.code on the error of a catch. |
ERR_TYPE_MISMATCH | 7 | A value of a type that does not fit there. | Adding a string and a number; float("3.5"); calling something that is not a function; an argument of another type, in almost every function. |
ERR_INVALID_VALUE | 8 | The type is right, the value is not. | JSON.parse with a text that is not JSON; Input.map with a key that does not exist; a tile outside 0-255 in a GridMap; a strip of more than 1024 LEDs. |
ERR_INVALID_ARG_NUMBER | 9 | Too many or too few arguments. | When you call a function of yours, a method or a module function with another number of arguments. With almost all of those in the standard library, the program does not even compile. |
ERR_EMPTY_LIST | 10 | The list is empty. | list.pop() with the list empty. |
ERR_INDEX_OUT_OF_RANGE | 11 | A position that does not exist. | An index outside a list, a string or an array, negative ones too; insert or pop of a list with an index that is not there; a cell outside a GridMap; an LED outside its strip; a byte outside its Bytes. |
ERR_KEY_NOT_FOUND | 12 | The key is not in the map. | map["key"] or map.pop("key") with a key the map does not have. |
ERR_INVALID_OPERATION | 13 | Something that cannot be done that way. | A method without parentheses (text.size); assigning to a read-only property (Display.dt = 1); an operator the object does not have, such as adding a number to an instance of a class without operator +; using an LED strip that is already closed. |
ERR_DIVISION_BY_ZERO | 14 | Division by zero. | /, div or mod with a 0 on the right, with decimals too: 1.0 / 0. |
ERR_NOT_IMPLEMENTED | 15 | Something not done. | Not used in 0.9.7. |
ERR_MODULE_NOT_FOUND | 16 | A module that was not opened. | Using a module without its use, or before it runs: Module 'Math' not found or not imported. (see use). |
ERR_CIRCULAR_IMPORT | 17 | A circular import. | Not used: a circular import doesn't compile (see import). |
ERR_FILE_NOT_FOUND | 18 | The file is not there. | File.open in "r" mode, File.delete, File.rename or File.copy with a file that does not exist; Bitmap.load and GridMap.load with one they cannot open; File.setUnit("sd:") on a board without an SD card. |
ERR_FILE_EXISTS | 19 | The file already exists. | Not used in 0.9.7. |
ERR_PERMISSION_DENIED | 20 | Not allowed. | Calling a private method (def private) from outside its class. |
ERR_IS_A_DIRECTORY | 21 | It is a folder, not a file. | Not used in 0.9.7. |
ERR_NOT_A_DIRECTORY | 22 | It is a file, not a folder. | Not used in 0.9.7. |
ERR_FILE_READ_FAIL | 23 | It could not be read. | Reading from a closed file, or a failure while reading it. |
ERR_FILE_WRITE_FAIL | 24 | It could not be written. | Writing to a file opened with "r" or already closed; File.mkDir and File.rmDir when they cannot; Bitmap.writeHeader and Bitmap.writeLine. |
ERR_INVALID_FORMAT | 25 | The file does not have the right format. | GridMap.load with a file that is not a JARU .gmap, is from another version or is cut short. |
ERR_NETWORK_UNAVAILABLE | 26 | There is no network. | Not used in 0.9.7. |
ERR_CONNECTION_REFUSED | 27 | The other side refuses the connection. | Not used in 0.9.7. |
ERR_CONNECTION_RESET | 28 | The other side has cut the connection. | Not used in 0.9.7. |
ERR_TIME_OUT | 29 | The waiting time is over. | Not used in 0.9.7. |
The ones marked “Not used in 0.9.7” exist, but the VM of this version never uses them. Network failures are not exceptions either: Http and MQTT report them with their own functions, such as Http.status(), Http.error() and MQTT.isConnect() (see Http and MQTT).
Error values
Some module functions do not throw an exception when they fail: they return an error value, and the program goes on as if nothing had happened. It mostly happens when they are called the wrong way, with too many, too few or wrongly typed arguments. And in some failures that the page of each module or type describes, such as text.toFloat() with a text that is not a number (see Strings).
use Power
var r = Power.wakeOnGPIO() // the pin is missing
println(r) // Error 9
println("The program goes on")
An error value prints as Error and a number: the value of the error's constant. Error 9 is ERR_INVALID_ARG_NUMBER.
On a board, some of these functions also write a warning through the serial port: the message (Wrong argument count in 'Power.wakeOnGPIO()'.), Traceback : and the VM's stack. On Windows they say nothing.
An error value behaves like this:
trydoes not see it: it is not an exception.- In an
ifit counts as true. - It is not equal to its constant:
r == ERR_INVALID_ARG_NUMBERgivesfalse. Two error values with the same number are equal to each other. - Calling a method of it throws
ERR_UNKNOWN, and adding it to a string,ERR_TYPE_MISMATCH. type()givestError, like the error of acatch.
Telling whether a value is an error
With type(): an error value gives tError.
var text = "abc"
var number = text.toFloat()
if (type(number) == tError) then
println("Not a number: ", number) // Not a number: Error 7
end
println(type(2.5) == tError) // false
Better still is not to get there: call each function with the arguments its page asks for. And if a call does not do what you expect, print what it returns: if it shows Error and a number, look it up in the table of constants.
When you assign a property
The properties of modules and of VM objects (a sprite, a scene…) check the value you give them. With one that is not valid, it depends on the property:
- Some throw an exception, which
trycatches:Display.fps = "fast"throwsERR_TYPE_MISMATCH. - Others do nothing, without warning: the property stays as it was.
Display.showBG = "yes"leavesshowBGatfalse. - And others write a warning to the error output and do not change anything either:
sprite.x = "one"writes[ ERROR ] In 'x',andxstays as it was.
use Display
try
Display.showBG = "yes"
println("showBG is still ", Display.showBG)
Display.fps = "fast"
println("This line does not run")
catch (var e)
println(e)
end
showBG is still false
ERR_TYPE_MISMATCH: Display.fps must be a number.
The properties of your classes and the fields of your structs check nothing: they keep any value.
exit
exit ends the program at once, from anywhere: inside a function, a loop or a try. It takes an integer after it, the exit code, with or without parentheses: exit 1 or exit(1). Without a number it does not compile.
func divide(a, b)
if (b == 0) then
println("Cannot divide ", a, " by 0")
exit 1
end
return a div b
end
println(divide(10, 2))
println(divide(7, 0))
println("This line does not run")
It prints:
5
Cannot divide 7 by 0
And to the error output, one line for each running call, from the innermost to <main>():
> Exit :[line 4] in divide() with code 1 <
> Exit :[line 10] in <main>() with code 1 <
trydoes not catch it.- The code only appears in those lines: it changes nothing else. It has to be an integer: with anything else, the line shows a meaningless number.
- On Windows, with Run, those lines come out at the end of the console, and the Output copies them in red, like the errors.
- On a board,
exitdoes not leave the program stopped: the VM starts it again, as when it ends by itself (see Your program on a board). To keep it still, end it with a loop that never ends, such aswhile (true) pause(1000) end. exitis a statement, not a function: you cannot use it inside an expression.
vmerror()
vmerror() is a function of the standard library that should tell the code of the last error.
vmerror() always returns 0In 0.9.7, vmerror() returns 0 even when there have been errors, so it cannot tell you whether something failed. For exceptions, use try and catch; for error values, look at what each function returns.
All the known issues of JARU, together, are in Known issues.