CHAPTER 8
Exception handling is a common feature in modern programming language. Julian faithfully implements it in line with Java/C#'s error handling mechanism, through the special type Exception and try-catch-finally
structure.
The fundamental class in Julian's error system is System.Exception. This class contains several important members that can be used by programmers. First, you may call one of its constructors:
Exception(string message);
Exception(string message, Exception cause);
The message and current stack are also exposed through methods.
string getMessage();
string[] getStackTrace();
Exception getCause();
For most parts, Exception is just plain class. Its only specialty lies in its ability to be thrown from the code or engine. To throw an exception from the code, use throw statement.
throw new Exception("message");
Sometimes, though, the script engine will create and throw exceptions. So be prepared.
int x = 5 / 0; // throws System.DivByZeroException
Once an exception is thrown, it aborts the normal code path and directly jumps out of current frame and all the frames underneath recursively, effectively unwinding the entire stack until it hits the bottom and gets processed by the engine infrastructure. Crashing a background thread will have the exception eaten silently; faulting the main thread, however, will terminate the entire Julian engine. For more about threads, see Threading.
There is, however, a way to block the bubbling of exception. One can use try-catch
statement to enclose the exception-prone code and proactively handle the error before it crashes the entire thread.
try {
throw new Exception("message");
} catch (Exception ex) {
Console.println("Saw an exception - " + ex.getMessage());
Console.println("But let's continue.");
}
There could be more than one catch clause. In runtime these clauses will be matched in their natural order until one is found that can handle the thrown exception. Remember this is not a search for the most perfect match. Rather it's only about finding the first one that is compatible.
try {
throw new MyException("message");
} catch (Exception ex) {
Console.println("Handled as Exception"); // Hit
} catch (MyException mex) {
Console.println("Handled as MyException"); // Not hit
}
The exception will be handled by only one catch clause. If none of them matches, the exception will escape and continue its journey of descending the stask. Nonetheless, one may use finally
clause to perform some operation regardless of whether an error has been thrown or matched in any catch clause.
void fun(int i){
switch(i){
case 1: throw new Exception1();
case 2: throw new Exception2();
case 3: throw new Exception3();
}
}
try {
fun(arg);
} catch (Exception1 ex) {
Console.println("Handled as Exception"); // Hit when arg == 1
} catch (Exception2 ex) {
Console.println("Handled as Exception"); // Hit when arg == 2
} finally {
Console.println("Finally..."); // Always hit, even if fun threw Exception3 or didn't throw at all.
}
An interesting anecdote about finally clause is that it cannot change return value. By change I mean the direct mutation of the value itself, not changing a value referred by it (a field member or array element, for example). This immutability has something to do with the implementation details of script interpretation, but in general such behavior is consistent with that of Java and C#.
int fun(int x){
try {
return x;
} finally {
x++; // does get executed ...
}
}
int x = fun(5); // but x is still 5
The stack trace of Julian exception has a fixed format. The Julian language will continue honoring this format so feel free to parse them. An example of the stack trace is given here:
System.Exception: a business error occurred.
at funC(Integer) (E:\JulLib\MyModule1\file.jul, 12)
at funB(Integer) (E:\JulLib\MyModule2\Common\commons.jul, 8)
at funA(Integer) (E:\JulLib\MyModule2\def.jul, 4)
from (E:\Sandbox\Scripts\entrance.jul, 15)