– Typee Documentation –

3 – Typee Language Description

3.13 – Exceptions

Typee manages exceptions. Exceptions are faulty execution of code, such as division by 0 or out-of-bounds indexing, for instance. Typee uses a Python-like naming. C++ and Java programmers should nevertheless get no difficulty to understand and to use this concept.
Exceptions can be raised by code, and catched in caller functions for the processing of the detected errors.

Every exception inherit from base class Exception. There are built-in exception in Typee and programmers can define their own exceptions as long as they all inherit from Exception.

We first group here Typee syntax related to exceptions that has been already described in previous sections. We then list all Typee built-in exceptions. We finally provide some examples of catching exceptions, raising exceptions and creating exceptions.

3.13.1 Exceptions – related Typee syntax

3.13.1.1 try statement

The formal EBNF specification in Typee is this:

<try statement>   ::= 'try'   <statements block>
                          <try except>     <statements block>
                          (<try except>    <statements block>)*
                          [<try otherwise> <statements block>]
                          [<try finally>   <statements block>]

<try except>      ::= 'except' '(' [ (<try except expr> | 'all' ) ] ')'
<try except expr> ::= <expression> ['as' <identifier>]
                          (',' <expression> ['as' <identifier>])*

<try otherwise>   ::= 'otherwise'

<try finally>     ::= 'finally'

The keyword try tags a block of statements in which any raised exception will be caught. As soon as an exception is raised by some statement in this block, the execution of the statements in this block stops and the chain of except clauses is checked against the type of the raised exception.

Each except clause specify one or many types of exceptions for which their associated block of statements will be processed. The keyword as names the raised exception which can then be addressed in the statements block after it.

If the type of the raised exception matches the type expressed in an except clause, the statements block associated with this except clause is immediately processed.

If none of the exceptions types expressed in the chain of except clauses matches the raised exception, then the otherwise clause is used and its associated block of statements is processed, if this clause is present.

If a finally clause is present, the raised exception having been caught by a previous except clause or not, the statements block associated with clause finally is processed. This means that when a finally clause is present, it is always processed.

Example:

try {
    for( float n in [5.0:-1.0:-1.0] )
        print( 1.0 / n );
}
except ZeroDivisionException {
    print( "Infinity" );
}
otherwise {
    print( "otherwise clause" );
}
finally{
    print( "finally clause is always processed." );
}
// prints:
// 0.2       - 1.0 / 5.0
// 0.25      - 1.0 / 4.0
// 0.333333  - 1.0 / 3.0
// 0.5       - 1.0 / 2.0
// 1.0       - 1.0 / 1.0
// Infinity  - 1.0 / 0.0
// finally clause is always processed.
3.13.1.2 raise statement

The formal EBNF specification of statement raise in Typeeis:

<raise statement>  ::= 'raise' <expression> ['from' <expression>]

Statement raise is associated with exceptions. This statement raises exceptions at the discretion of programmers. If this statement is processed in an environment where exceptions are caught, the associated raised exception will be caught and processed accordingly. The expression next to keyword raise should be the constructor of an Exception or an instance of an exception class. It may be followed by keyword from and another exception expression. This secondary information may help producing more precise errors messages when raising exceptions. In Python, this is used to chain exceptions. This is its use also in Typee.

See example later in this section of Typee documentation.

3.13.2 Exceptions are classes

Class Exception is the base class for all exceptions in Typee.

class Exception {

    Exception( const str text, const ... args ){
       me.text = text;
       me.args = [arg for arg in args];
    }

    str cast operator str(){
        str my_txt = me.txt;
        for (arg in me.args){
            try{
                my_txt += ' ' + str( arg );
            }
            except{
                nop;
            }
        }
        return my_txt;
    }
    
  :protected:
    const str  text;
    const list args;
}

The above code is an illustration of base class Exception with a quite naive programming. The cast operator str casts the exception to a string that contains its associated text and as much optional arguments as possible, separated with spaces. This operator is automatically called by Typee anywhere a string would be expected while passing an instance of this class. This is the case, for instance, when printing an instance of an exception.

Example:

print( Exception("Exception raised", "line", 125, "- unknown cause.") );
// prints:  Exception raised line 125 - unknown cause.

3.13.3 Built-in Exceptions

Typee built-in exceptions are many. We list all of them in their alphabetical order here. For the most of their use, you will intercept them in try-except clauses.

exception class inheritance comments
ArithmeticException Exception Base class for all other arithmetic errors
AssertException Exception raised by assert
DimensionException Exception trying to index a non existing dimension of a container
EnsureException AssertException raised by ensure
FileAccessException FileException can’t grant access to a specified file
FileClosedException FileException trying to operate a closed file
FileEOFException FileException end of file reached while reading data from file
FileException Exception Generic file exceptions, base for all other file exceptions
FileExistsException FileException trying to create an already existing file
FileNotFoundException FileException trying to access a non existing file (most often: erroneous file path)
FileNotOpenedException FileException trying to operate a not yet opened file
FileOpenedException FileException trying to open an already opened file
FilePermissionException FileException trying to operate a file with wrong permissions
FilePosException FileException Erroneous cursor or index positionning in a file
FileRWModeException FileException trying to write in a read-only file or trying to read in a write-only file
FloatingPointException ArithmeticException erroneous final float value
KeyNotFoundException Exception the specified key is not present in a map
ModuleException Exception any import error: trouble when importing a module or not found identifier in imported module
ModuleNotFoundException ModuleException imported module cannot be located
NotFoundException Exception a searched item has not been found in a container
NotMutableException Exception trying to modify an entity of constant value
OpSysException Exception any Operating System error
OutOfBoundsException Exception indexing a container (array, list or file) with an out of bounds index value
OverflowException ArithmeticException too big value
RequireException AssertException raised by require
StringFormatException Exception erroneous format string
SystemError Exception any other kind of exception (run-time error for instance)
TypeException Exception wrong type or type misuse
UnderflowException ArithmeticException too small value
ValueException Exception a wrong or forbidden value has been used
ZeroDivisionException ArithmeticException division by zero

The watchful reader will eventually ask how could most of these exceptions be raised at run-time while we will just be translating a Typee program into any other programming language, and since those targeted languages may lack of such an exception mechanism or of such exceptions values?
Well, indeed this is a very good question.
We will completely address it when talking about Typee programs, but we can say right now that any Typee program, once translated into a targeted language, will get all the needed stuff around the main() function (the one that is run as the entry point of the program for the final Operating System). This stuff will be something like an embedding try statement which will intercept as much as possible of the above exceptions from the final OS and the targeted programming language and which will raise back the related Typee exception. This way, your Typee code will just have to intercept Typee Exception, whatever the targeted programming language and the final Operating System it will run on.
Of course, should some target platform (i.e. OS plus programming language) lack the exception mechanism or lack some Typee exceptions, those exceptions will not be intercepted on this targeted platform.

3.13.4 Creating your own Exceptions

This is just inheritance! Let’s illustrate it with an example. Caution, this example is a little big long but it proposes many Typee features and many OOP features that the reader should enjoy. As an exercice, you could try to translate it in your favorite programming language.

So, we want to define a dedicated class for a specific type of container: sparse matrices.
Sparse matrices are huge matrices with most of their entries set to null, or zero, or no-meaning value and a very few of their entries that contain value. Programming them as a 2D-array is a huge waste of space since most of their content is of no value. Moreover, it may be that their dimensions will be too huge for them to be placed as a whole in volatile memory.
An example of such matrices? Netflix notation of hundred of thousands video content by hundred of million of users. This is about 10,000 Giga-entries. No way to keep it in a 10 TBytes volatile memory. Meanwhile, this hundred of million of users may have noted a mean of 10 or 20 movies, which is about a few GBytes of notations.

A way to code a sparse matrix is to associate its two coordinates [raw][code] as a key with the associated value. Let’s make such matrices inherit from this Typee built-in container. Let’s say that they will contain a single type for their content, as a template type ItemT.

class SparseMatrix<ItemT> {

    SparseMatrix( const uint32 max_raws, const uint32 max_cols ){
        me.raws, me.cols = max_raws, max_cols;
    }
    
    ItemT operator [] ( const uint32 raw, const uint32 col ){
        return me.data [ [raw, map] ];
    }
    
  :private:
    uint32      raws, cols;
    map<ItemT>  data;
}

This is a very good canvas for our programming, but it won’t work as is. Why? Let’s use it as is.

SparseMatrix my_mat = SparseMatrix( 100_000_000, 100_000 );
my_mat[0,0] = 5;  // should raise an KeyNotFoundException!

Here, in operator[], me.data is an empty map just after creation time. Trying to access item with key [0,0], to assign it a value, is an error since this this indexed item does not yet exist. So, we have to take care of this in operator [].

class SparseMatrix<Itemt> {

    SparseMatrix( const uint32 max_raws, const uint32 max_cols ){
        me.raws, me.cols = max_raws, max_cols;
    }
    
    ItemT operator [] ( const uint32 raw, const uint32 col ){
        try {
            return me.data [ [raw, map] ];
        }
        except KeyNotFoundException{
            me.data [ [raw, map] ] = ItemT( 0 );
            return me.data[ [raw, map] ];
        }
    }
    
  :private:
    uint32      raws, cols;
    map<ItemT>  data;
}

We’ve added a try statement into the definition of operator[]. Now, when accessing a not yet created item in the sparse matrix, the raised KeyNotFoundException is intercepted and processed this way:

  • we first create the related entry with list [raw, col] as its key and we assign it with the null value of type ItemT;
  • we then return a reference to this newly created entry.

This supposes that the template type ItemT gets a way to provide null values either because it is an integer or floating point scalar type, or because it is a class with a “null” constructor.

Now, we can address any item in the sparse matrix even if it is absolutely empty:

SparseMatrix my_mat = SparseMatrix( 100_000_000, 100_000 );
print( my_mat[0,0], my_mat[99_999_999, 99_999] );  // prints:  0 0

Furthermore, we can now assign values to not yet existing items as well as modify values of already existing items.

SparseMatrix my_mat = SparseMatrix( 100_000_000, 100_000 );
my_mat[0,0] = 5;
print( my_mat[0,0], my_mat[99_999_999, 99_999] );  // prints:  5 0
my_mat[0,0] = 3;
print( my_mat[0,0], my_mat[99_999_999, 99_999] );  // prints:  3 0

Well, we are not fully done with this code. Now, we can get the “empty” or “null” value for items when asking for the value of not-initialized items. But we have programmed this in a way where the missing item is created into our internal structure, the map with identifier data. This was mandatory to be able to assign some value to the indexed item. We do need to create the item in the internal storage structure. But when just dealing with the value of an item, we do not want to create it in the sparse matrix if this item is null.

To obtain this effect, we have to define a const operator. While returning a constant item, this operator will not be called when assigning a value to this item. Meanwhile, when only dealing with the value of the indexed item, in an expression for instance, or as a constant argument of a function, Typee will first check for the constant version of operator [] if defined.

Here it is:

class SparseMatrix<ItemT> {

    SparseMatrix( const uint32 max_raws, const uint32 max_cols ){
        me.raws, me.cols = max_raws, max_cols;
    }
    
    itemT operator [] ( const uint32 raw, const uint32 col ){
        try {
            return me.data [ [raw, map] ];
        }
        except KeyNotFoundException{
            me.data [ [raw, map] ] = ItemT( 0 );
            return me.data[ [raw, map] ];
        }
    }
    
    const ItemT operator [] ( const uint32 raw, const uint32 col ){
        try {
            return me.data [ [raw, map] ];
        }
        except KeyNotFoundException{
            return ItemT( 0 ) ;
        }
    }
    
  :private:
    uint32      raws, cols;
    map<ItemT>  data;
}

Finally, just to validate the sparsing nature of SparseMatrix, let’s add a new casting operation on it: operator cast str (). This way, we will be able to print the content of a sparse matrix with no effort.

class SparseMatrix<ItemT> {

    SparseMatrix( const uint32 max_raws, const uint32 max_cols ){
        me.raws, me.cols = max_raws, max_cols;
    }
    
    ItemT operator [] ( const uint32 raw, const uint32 col ){
        try {
            return me.data [ [raw, map] ];
        }
        except KeyNotFoundException {
            me.data [ [raw, map] ] = ItemT( 0 );
            return me.data[ [raw, map] ];
        }
    }
    
    const ItemT operator [] ( const uint32 raw, const uint32 col ){
        try {
            return me.data [ [raw, map] ];
        }
        except KeyNotFoundException{
            return ItemT( 0 ) ;
        }
    }
    
    str operator cast str() {
        str my_content = "";
        for( key, value in me.data )
            my_content += " " + str(key) + ":" + str(value);
        return my_content;
    }

  :private:
    uint32      raws, cols;
    map<ItemT>  data;
}

We can now easily print the matrix content.

SparseMatrix my_mat = SparseMatrix( 100_000_000, 100_000 );
my_mat[0,0] = 5;
my_mat[99_999_999,99_999] = 3;
print( my_mat );
// prints:  [0, 0]:5 [99999999, 99999]:3

But wait. We were describing here how to create our own exceptions… So, what all the above stuff is useful for?
Here we are, where we can provide dedicated exceptions with sparse matrix when some error has been found. The simpler one is an out-of-bounds indexing. But there could be many other ones. We will group all of these under the same label SparseMatrixException

class OutOfBoundsSparseMatrixException : OutOfBoundsException {
    OutOfBoundsSparseMatrixException( const str    raw_col_txt,
                                      const uint32 index,
                                      const uint32 max_index ){
        OutOfBoundsException( "Out of bounds exception in sparse matrix,",
                              raw_col_txt, index,
                              "is not in bounds [ 0,", max_index, ")" );
    }
}

class OutOfBoundsRawException : OutOfBoundsSparseMatrixException {
    OutOfBoundsRawException( const uint32 raw,
                             const uint32 max_raw ){
        OutOfBoundsSparseMatrixException( "raw", raw, max_raw );
    }
}

class OutOfBoundsColException : OutOfBoundsSparseMatrixException {
    OutOfBoundsColException( const uint32 col,
                             const uint32 max_col ){
        OutOfBoundsSparseMatrixException( "column", col, col_raw );
    }
}

class SparseMatrixException : Exception {
    SparseMatrixException(){
        Exception( "SparseMatrix exception - unknown cause" );
    }
}

Now, we can intercept the related exceptions in our code and raise back our newly defined exceptions. Quite simple to do.

class SparseMatrix<ItemT> {

    SparseMatrix( const uint32 max_raws, const uint32 max_cols ){
        me.raws, me.cols = max_raws, max_cols;
    }
    
    itemT operator [] ( const uint32 raw, const uint32 col ){
        try {
            if( raw < 0 or raw >= me.raws )
                raise OutOfBoundsRawException( raw, me.raws );
            if( col < 0 or col >= me.cols )
                raise OutOfBoundsColException( col, me.cols );
            return me.data [ [raw, map] ];
        }
        except KeyNotFoundException {
            me.data [ [raw, map] ] = ItemT( 0 );
            return me.data[ [raw, map] ];
        }
        otherwise {
            raise SparseMatrixException();
        }
    }
    
    str operator cast str() {
        str my_content = "";
        for( key, value in me.data )
            my_content += " " + str(key) + ":" + str(value);
        return my_content;
    }

  :private:
    uint32      raws, cols;
    map<ItemT>  data;
}

Let’s now try it.

SparseMatrix<uint16> my_mat = SparseMatrix<uint16>( 100, 10 );
my_mat[ 10, 9 ] = 10009;
print( my_mat );
// prints: [10, 9]: 10009

try{
    print( my_mat[1, 1] );  // "const ItemT" signature called here
}
except Exception as e {
    print( e );  // not interecepted here but in operator code
}
// prints: 0

try{
    print( my_mat[ -1, 10 ] ); // raw index is out of bounds
}
except Exception as e {
    print( e );
}
// prints:
// Out of bounds exception in sparse matrix, raw -1 is not in bounds [0, 100)

try{
    print( my_mat[ 10, 100 ] ); // col index is out of bounds
}
except Exception as e {
    print( e );
}
// prints:
// Out of bounds exception in sparse matrix, col 100 is not in bounds [0, 10)

try{
    print( my_mat[ 0, 0 ] = -1;
    // value -1 is not of type uint16, intercepted by 'otherwise'
}
except Exception as e {
    print( e );
}
// prints: SparseMatrix exception - unknown cause

3.13.5 Conclusion

Next section formerly explains statement embed which is so specific to Typee.

< previous (3.12 classes) | (3.14 embedding code) next >