The purpose of this section is to illustrate some of the ideas presented in the last section in a longer piece of code. For simplicity, easy comparison, and to avoid having to present planning steps not relevant to this section, consider one way of adding appropriate exceptions to the Module *Fractions* first developed in chapter six.

Three operations with fractions can be identified as ones that, if proper data is provided, there will be no error, but if improper data is passed the fractions are invalid. (This makes the module a good candidate for having some exceptions defined--it can control its own code, but not the mistaken calls of clients using erroneous data.) These are:

1. assignment of a zero denominator,

2. taking the inverse when there is a zero numerator, and

3. dividing when the divisor has zero numerator.

These are similar errors, and all could be given the same exception identifier, but for the purpose of illustrating features of exceptions, all three will be treated separately.

In the definition module that follows, alterations have been made to the original in accordance with the remarks in the last section.

DEFINITIONMODULEFractions; (* Written by R.J. Sutcliffe *) (* using ISO Modula-2 *) (* to illustrate exceptions use in libraries *) (* last revision 1994 05 31 *)TYPEFraction =ARRAY[1 .. 2]OFINTEGER; (* the first component is the numerator; the second the denominator *)PROCEDUREAssign (m, n :INTEGER) : Fraction; (* If n is not equal to zero, then the fraction returned has m as numerator and n as denominator. Otherwise the exception zeroDenominator is raised. *)PROCEDURENumerator (x : Fraction) :INTEGER; (* the numerator of the fraction is returned *)PROCEDUREDenominator (x : Fraction) :INTEGER; (* the denominator of the fraction is returned *)PROCEDURENeg (x : Fraction) : Fraction; (* Pre: the fraction returned has the numerator negated *)PROCEDUREInv (x : Fraction) : Fraction; (* If the numerator of x is not equal to zero then the fraction returned has numerator and denominator swapped. Otherwise the exception noInverse is raised. *)PROCEDUREAdd (x, y : Fraction) : Fraction; (* The fraction returned is the sum x plus y *)PROCEDURESub (x, y : Fraction) : Fraction; (* The fraction returned is the difference x minus y *)PROCEDUREMul (x, y : Fraction) : Fraction; (* The fraction returned is the product of x and y *)PROCEDUREDiv (x, y : Fraction) : Fraction; (* If the numerator of y is not equal to 0, then the fraction returned is the quotient of x by y. Otherwise, the exception zeroDivide is raised *)TYPEFracExceptions = (zeroDenominator, noInverse, zeroDivide);PROCEDUREIsFracException ():BOOLEAN; (* Returns TRUE if the current coroutine is in the exceptional execution state because of the raising of an exception from FracExceptions; otherwise returns FALSE. *)PROCEDUREFracException (): FracExceptions; (* If the current coroutine is in the exceptional execution state because of the raising of an exception from FracExceptions, returns the corresponding enumeration value, and otherwise raises an exception. *)ENDFractions.

Note in the following implementation that a choice has been made to report any exception occurrence and print the associated string in the termination part of the module.

IMPLEMENTATIONMODULEFractions; (* Written by R.J. Sutcliffe *) (* using ISO Modula-2 *) (* to illustrate exceptions use in libraries *) (* last revision 1994 05 31 *)FROMEXCEPTIONSIMPORTExceptionSource, AllocateSource, RAISE, IsExceptionalExecution, IsCurrentSource, CurrentNumber, GetMessage;FROMSTextIOIMPORTWriteString, WriteLn, SkipLine, ReadChar;VARfracExSource : ExceptionSource;PROCEDUREAssign (m, n :INTEGER) : Fraction;VARtemp : Fraction;BEGINIFn = 0THENRAISE (fracExSource,ORD(zeroDenominator), "Cannot assign fraction with zero denominator");ELSEtemp [1] := m; temp [2] := n;RETURNtemp;END;ENDAssign;PROCEDURENumerator (x : Fraction) :INTEGER;BEGINRETURNx [1];ENDNumerator;PROCEDUREDenominator (x : Fraction) :INTEGER;BEGINRETURNx [2];ENDDenominator;PROCEDURENeg (x : Fraction) : Fraction;BEGINx [1] := -x [1];RETURNx;ENDNeg;PROCEDUREInv (x : Fraction) : Fraction;VARtemp :INTEGER;BEGIN;IFNumerator (x) = 0THENRAISE (fracExSource,ORD(noInverse), "Cannot invert fraction with zero numerator");ELSEtemp := x [1]; x [1] := x [2]; x [2] := temp;RETURNx;END;ENDInv;PROCEDUREAdd (x, y : Fraction) : Fraction;VARtemp : Fraction;BEGINtemp [1] := x [1] * y [2] + x [2] * y [1]; temp [2] := x [2] * y [2];RETURNtemp;ENDAdd;PROCEDURESub (x, y : Fraction) : Fraction;BEGINRETURNAdd (x, Neg (y) );ENDSub;PROCEDUREMul (x, y : Fraction) : Fraction;BEGINRETURNAssign (x [1] * y [1], x [2] * y [2]);ENDMul;PROCEDUREDiv (x, y : Fraction) : Fraction;BEGINIFNumerator (y) = 0THENRAISE (fracExSource,ORD(zeroDivide), "Cannot divide by zero");ELSERETURNMul (x, Inv (y) );END;ENDDiv;PROCEDUREIsFracException ():BOOLEAN;BEGINRETURN(IsExceptionalExecution() )AND(IsCurrentSource (fracExSource) )ENDIsFracException;PROCEDUREFracException (): FracExceptions; (* The call to CurrentNumber will raise ExException automatically if this source didn't raise an exception. *)BEGINRETURNVAL(FracExceptions, CurrentNumber (fracExSource) );ENDFracException;VARerrorMessage :ARRAY[0..255]OFCHAR;BEGIN(* initialize *) AllocateSource (fracExSource);FINALLYIFIsFracException ()THENGetMessage (errorMessage); WriteString ("Program terminating because of exception"); WriteLn ; WriteString (errorMessage); WriteLn; WriteString ("Type return to continue"); SkipLine;END;ENDFractions.

There are slight dangers in putting code into a FINALLY clause that depends on the importation of some other module, as in this case.

First, if termination is caused by an exception during initialization, then it may be that the module required (*STextIO* here) has not yet been initialized (and therefore cannot be used correctly). This should not happen in this case, because all the exceptions that can be raised by this module are in code that is unlikely to be called during the initialization of library modules.

Second, modules are terminated in the reverse order that they are initialized. If the module being employed in FINALLY clause has already been terminated, it *may* be that some facilities it offers are no longer available. (An implementation may choose to close all channels during finalization, for instance). This latter problem can only be determined to exist by examining the implementation documentation.

Note that if either problem *does* exist, there is no work around, because:

Thecorrectnessof a program depends on the meaning of its individual modules, but any program whose meaning depends on the order of initialization (or finalization) of its modules is incorrect.

In this case, the following simple application was run to do a limited test of the new exceptions, by forcing one of them to be raised:

MODULETestFractions;IMPORTFractions;VARp, q, r : Fractions.Fraction;BEGINp := Fractions.Assign (0, -3); q := Fractions.Assign (4, -3); r := Fractions.Div (q, p);ENDTestFractions.

The exception was triggered, and checking the output log after the run was complete revealed its contents as:

** Run log starts here ** Program terminating because of exception Cannot divide by zero Type return to continue