Loglan'82 Programming Language
______________________________

Signallling Exceptional Situations & Treatment of Exceptions

1. MOTIVATIONS

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.

2. SYNTAX

Treatment of signals in Loglan is distributed onto different fragments of a program.

In order to do with signals and exceptional situations one must:

  1. declare a signal,
  2. propagate the signal toward a handler,
  3. write one or more modules which will handle the signal,
  4. exit from a handler,
  5. the latter action may necessitate the execution of the lastwill instructions for the instances of modules that may be killed by such an exit.

3. SEMANTICS

(a) Signal declaration

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.

(b) Modules handling signals

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.

(c) Raising a signal

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

(d) How to end a service (handling) of a signal?

When the handler finishes the service of a signal, the execution continues in a module determined by the appropriate instruction which should be:

(e) Lastwill

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.

(f) Inheritance vs. signalling

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.


GMyAS

Last update Wed 3 May 1995