Programs which we are constructing should be robust, i.e.
they should not fail even if the values of parameters(data) for
a procedure, function etc.are out of the domain of a procedure
in question. The minimal requirement would be: if the data do
not belong to the assumed domain of program then this information
should be displayed. However, in many cases the programmer is
able to react at errors which appear during the execution of a
program.
Sometimes she(he) is able to use the mechanisms build for the
use in unusual situations in a non-standard, elegant way (see
the example MAZE below). However we are not suggesting this to
turn into your style or "maniere" of writing programs.
Another keyword applicable here is security. If we wish to construct
a secure program then we need appropriate tools to assure this
property.
What will be called an exceptional situation?
Example 1
Let us consider the data structure STACKS with operations
It will be an exceptional situation, if one will call pop for
an empty stack or will call push for a full stack. Obviously we
can require that the user never calls pop(s) when s in an empty
stack, but our program can be more flexible i.e. clever and we
can foresee eventual unproper usage of the partial operations
from our module.
Another example of exceptional situation arrive when one thinks of getting an element of an array in such a way that the index of the element is outside the limits of the array. Obviously we can advise to replace any occurrency of a subscripted variable A[i] by a guarded command like the following one
if lower(A)-1 < i and i < upper(A)+1 then y := A[i] else (* put here your reaction for the array index error *) fi
which replaces the instruction
y := A[i]
.
The advice consequently applied throughout a program would make
it unreadable, thus opening the doors for numerous errors. A solution
lies in signalling an error or exception and propagating such
a signal to the nearest object or dynamic instance of a procedure
which contain a recipe (a handler) how to react. Some exceptional
situations are detected automatically by the Loglan system (see
the list of run-time errors) . Some exceptions may be signalled
trough the raise instruction.
Treatment of signals in Loglan is distributed onto different fragments of a program.
In order to do with signals and exceptional situations one must:
A signal declaration consists of keyword signal and the
list of names of signals we are going to use together with eventual
lists of formal parameters (the parameters of signals are not
obligatory however ), e.g.
signal empty_stack(s:stack), no_record(r:key), stackoverflow;
Declaration of signal(s)may appear anywhere in the declaration
part of a module.
Let Ni be a name of a signal and Si a sequence of
instructions. We assume that names of signals are visible (declared).
Then we can write ( as the end part of the declaration part of
a module) the following definition of handling signals:
handlers when N1: S1; when N2: S2; .............. others Sn end handlers
The sequence of instructions Si is called a handler for the signal
Ni. The sequence Sn appearing after the clause others is
a universal handler for any signal which can be propagated into
an object of this module and which is not listed above. Si - can
contain any legal instructions and moreover instructions return,
wind, terminate which determine the way in which this handler
will be finished, and the place where the execution of program
will be continued.
An instruction raising a signal contains the keyword raise
and the name of a signal which we would like to be handled, the
list of actual parameters can be given if the signal was declared
with a list of formal parameters.
Example 2
raise found;
if x> size then raise too_much fi;
raise empty_stack(s)
Meaning:
A signal can be raised during an execution of a program either
by run-time system (e.g. MEMERROR) or by program (cf. raise
instruction above) Suppose that a signal f has been raised in
an object M. For brevity, we shall use the name object also for
dynamic instances of blocks, functions, procedures. If M contains
a handler for f then this handler is executed. More precisely:
an instance H of this handler is created, the static father of
H is M, the dynamic father of H is the module in which the signal
was raised, in this case it is again M. Otherwise the search of
a handler for a signal is continued in the dynamic father of the
object M. One can also say that the signal has been propagated
to the dynamic father of the object. There are three modifications
to this scheme: 1. if M is an exception handler then signal is
propagated to the object in which M is declared 2. if M is a coroutine
or the whole program then the whole program is terminated' 3.
If M is a process then the process is terminated. When an object
containing a handler for the signal was found, the object of handler
is created. Its dynamic father is the object in which the signal
was raised, its static father is the object in which a declaration
of the handler was found.
Example 3
program three; unit record : class; var key : T; end record; signal no_record(k:T); unit search : function(k:T): record; var r:record; handlers when eof_error: raise no_record(k); end handlers; begin do get(f,r); if equal(r.key,k) then result:=r; exit fi; od end search; handlers when no_record : ... end handlers; begin ... r:=search(k); ... end three
In this example the signal no-record is raised when the system
raises the signal eof. Thus we reinterpret the eof signal and
adapt it to our aims.
Example 4
program exercise4; signal f; unit A: procedure; begin... raise f; ... end A; unit B : procedure; handlers when f : ... end handlers; begin call A; ... raise f; end B; handlers when f :... end handlers; begin (*main program *) ... raise f; ... call B; ... call A end exercise4.
In this example one signal has two handlers. The signal f raised
in the main program or in procedure A will be served by the handler
declared in the main program module. The signal f raised in procedure
B either directly: by raise, or indirectly by raise
in the procedure A called from B will be served by the handler
declared in procedure B.
.
When the handler finishes the service of a signal, the execution continues in a module determined by the appropriate instruction which should be:
When an object (or a dynamic instance of a block, procedure, function)
is terminated abnormally through the execution of wind
or terminate instructions, then the lastwill - a sequence
of instructions will be executed before such an object will be
terminated. The sequence of lastwill instructions must
be located at the end of a module and is announced by a
label lastwill:. A normal termination of an object will
never cause the execution of lastwill. Termination of a dynamic
instance of block, function, procedure causes its deallocation.
In the following example we shall demonstrate the usage of the
wind instruction in a handler. When executed it causes
that all objects - instances beginning from the module in which
a signal was raised and ending in the module containing the handler
are closed and terminated. The execution will be continued in
the object containing the handler from the point which caused
creation. In order to assure the smooth continuation the language
offers a possibility to define a lastwill instruction(s).
Example 5
program MAZE; var A : arrayof arrayof boolean, i,n : integer, there_is_a_path : boolean; signal Found; unit PATH : procedure (i,j : integer); (* the procedure makes one move from(i,j) *) begin if A(i,j) then (* we can go through (i,j) field *) if i=n and j=n then raise Found fi; if i< n then call PATH(i+1,j) fi; if j< n then call PATH(i,j+1) fi; fi; last_will : write(i,j); (* the path will be printed in the reverse order *) end PATH; handlers when Found : there _is_a_path :=true; wind end handlers; begin (** main program **) .... (* create a maze A etc. *) call PATH(1,1); if there_is_a_path then ... ... end MAZE.
In this example we can observe how the programmer turned unusual
situation of a raised signal into the desired one: through lastwills
she got a simple program to print the path through the maze.
Handlers (modules handling signals) behave similarly to virtual
procedures, i.e. the new handler from the prefixed module replaces
the module from the prefixing module.
Example 6
unit STACKS: class (type :telem); signal empty_stack(s:stack), stack_overflow(s:stack); unit stack : class (size:integer); hidden place, top; var place : arrayof taken, top: integer; unit pop: function:telem; handlers when conerror: raise empty_stack(this stack) end handlers; begin result :=place(top); (** here con_error signal can be raised by run_time_system **) top:=top-1 end pop; unit push : procedure (e:telem); begin if top> size then raise stack_overflow(this stack) fi; top:=top+1; place(top):=e end push; unit empty: function : boolean; begin result:=top< 1 end empty; unit increase : procedure (addition: integer); var i : integer, x : arrayof telem; begin array X dim (size+addition); size:=size+addition; for i:=1 to upper(place) do x(i):=place(i) od; kill(place); place:=X end increase. begin array place dim(1:size); end stack; handlers when empty_stack: write("empty stack"); terminate; when stack_overflow: write("stack overflow"); terminate; when conerror : write("error in stack increasing"); call endrun; end handlers; end STACKS;
Applications
Example 7
program APPLICATION_1; unit STACKS: ... unit element: ... ... pref STACKS(element) block var s1,s2 : stack ... handlers when stack_overflow : ... call s.increase(70); return; end handler; begin (*** block ***) ... s1:= new stack(c1); s2:= new stack(c2); ... call s1.push(e); ... y:=s2.pop; ... end (*block*); end APPLICATION_1
In this example the handler for stack_overflow from the prefixed
block overrides the handler given in the class STACKS.
Example 8
program ReversePolishNotation; (* Application 2 *) unit STACKS: class; ... end STACKS; ... unit element : class(sign:char); end element; ... pref STACKS(element) block; ... handlers when empty_stack: write(" error in expression_to many ( closing brackets "); terminate; end handlers; begin (*block *) while not eof do read(x); if x= ')' then (* take operators from stack until ( is met *) else .... fi od end (*block*) end ReversePolishNotation;
This example shows that there are situations in which the user of a class STACKS knows how to handle the signal empty_stack and solves the problem gracefully since in this case empty_stack means that the data were not a well formed expression.
Last update Wed 3 May 1995