Skip to main content

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 ]
====================================================================
  • Exception is the name of the error: one of the ERR_* constants.
  • Message says what happened.
  • Location and EXECUTION TRACE say 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, the catch block does not run.
  • If something fails, the rest of the try block is skipped: the program goes to catch, with the error in the variable. Then it carries on after the end.
  • catch always takes the parentheses and var, with a single variable. catch (e), without var, does not compile.
  • Each try has one catch, and only one. There is no finally.
  • 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:

PropertyWhat it isExample
e.nameThe name of the error, as a string."ERR_INDEX_OUT_OF_RANGE"
e.messageWhat happened."List index out of range."
e.tracebackA 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) is tError (see type).
  • Any other property throws ERR_ATTRIBUTE_NOT_FOUND.
  • The variable only exists inside the catch. If you use it after the end, the program stops with ERR_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 a try (see exit).
  • Failures of the VM itself: the 17th try open at the same time, and the VM's stack filling up.
Very deep recursion closes the VM without warning

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.

ConstantValueWhat it meansWhen it happens
ERR_UNKNOWN1An unclassified error.When you call a method of an error value.
ERR_OUT_OF_MEMORY2No 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_EXCEEDED3Too 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_FAILED4A check that does not hold.Not used in 0.9.7.
ERR_NAME_NOT_FOUND5A name that does not exist.A variable or a function misspelled, not declared or not declared yet: Undefined variable 'x'.
ERR_ATTRIBUTE_NOT_FOUND6A 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_MISMATCH7A 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_VALUE8The 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_NUMBER9Too 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_LIST10The list is empty.list.pop() with the list empty.
ERR_INDEX_OUT_OF_RANGE11A 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_FOUND12The key is not in the map.map["key"] or map.pop("key") with a key the map does not have.
ERR_INVALID_OPERATION13Something 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_ZERO14Division by zero./, div or mod with a 0 on the right, with decimals too: 1.0 / 0.
ERR_NOT_IMPLEMENTED15Something not done.Not used in 0.9.7.
ERR_MODULE_NOT_FOUND16A 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_IMPORT17A circular import.Not used: a circular import doesn't compile (see import).
ERR_FILE_NOT_FOUND18The 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_EXISTS19The file already exists.Not used in 0.9.7.
ERR_PERMISSION_DENIED20Not allowed.Calling a private method (def private) from outside its class.
ERR_IS_A_DIRECTORY21It is a folder, not a file.Not used in 0.9.7.
ERR_NOT_A_DIRECTORY22It is a file, not a folder.Not used in 0.9.7.
ERR_FILE_READ_FAIL23It could not be read.Reading from a closed file, or a failure while reading it.
ERR_FILE_WRITE_FAIL24It 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_FORMAT25The 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_UNAVAILABLE26There is no network.Not used in 0.9.7.
ERR_CONNECTION_REFUSED27The other side refuses the connection.Not used in 0.9.7.
ERR_CONNECTION_RESET28The other side has cut the connection.Not used in 0.9.7.
ERR_TIME_OUT29The 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:

  • try does not see it: it is not an exception.
  • In an if it counts as true.
  • It is not equal to its constant: r == ERR_INVALID_ARG_NUMBER gives false. 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() gives tError, like the error of a catch.

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 try catches: Display.fps = "fast" throws ERR_TYPE_MISMATCH.
  • Others do nothing, without warning: the property stays as it was. Display.showBG = "yes" leaves showBG at false.
  • And others write a warning to the error output and do not change anything either: sprite.x = "one" writes [ ERROR ] In 'x', and x stays 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 <
  • try does 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, exit does 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 as while (true) pause(1000) end.
  • exit is 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 0

In 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.