Skip to main content

START-UP AND SHUT-DOWN by Ronald van der Kamp

Shovelling dirt on the ST.

Many people have the experience that testing a Modula program  on 
the  ST  machines  can  be an awful job  because  the  system  is 
corrupted after a runtime-error.

For  some  time  now we can have the TOS in  ROM,  keep  a  reset 
resistent  RAM-disk in memory and use a  hard-disk.  Even  so,  a 
reset  of  the  machine is a nasty thing to do  and  booting  the 
system takes always more time then you feel it should take.
The  reason  for  the reset is mostly that you  land  up  with  a 
corrupted  or dead system;  the program killed by  the  operating 
system has left windows and workstations open, files open and all 
kind  of other (GEM) stuff is not 'cleaned-up' and left  for  the 
dogs.

When you construct your programs, you surely know what actions to 
take  when the program ends normal such as closing  windows,  set 
the  colour registers back to their old values,  make  the  mouse 
form an arrow again; mostly you also have an idea what to do when 
a runtime-error is generated. But how to do it?
For  a  long time I was very annoyed that I  could  imagine  what 
actions were to be done when a program ends normal, and also what 
to  do  when  he ends abnormal (runtime-error)  but  that  all  I 
could  expect  from the Modula runtime system were  stupid  alert 
boxes  with an 'OK' button.  (What is there OK about  a  runtime 
error  if afterwards the machine is corrupted and a system  reset 
is needed to go on?)
A  short  while ago I saw the implementation of a  module  called 
'Skeleton' written by Gert Slavenburg and in there was a solution  
found for the above mentioned problems.
What  I  never  imagened  to be  useful   were  the  undocumented 
possibilities  of the module 'GEMX' that is found in the  library 
that ex-TDI (now Modula-2 Software LTD, still in Bristol) suppies 
with their compiler.
In  this  article you will find a listing  from  an  experimental 
program  that demonstrates the possibilities for shutting down  a 
Modula program in the right way.
The  function of this program is writing the numbers  3120978  on 
the  VT-52 screen.  ( no graphical GEM stuff is used in order  to 
keep  this demo simple.) This program has the remarkable  quality 
to write this number also when a runtime error is generated.

As follows:

First let us take a look at the source code, starting on the next 
page.

(*--------------------our main program---------------*)

MODULE MOD0; (* test ShutDown *)
IMPORT Program;
IMPORT MOD1,MOD2;
FROM Terminal IMPORT Write,Read;
FROM Program IMPORT AddShutDowner;

VAR ch : CHAR;

PROCEDURE PROC9;
BEGIN Write('9') END PROC9;

PROCEDURE ReadPuin(VAR c : CHAR);
BEGIN
  Read(c);
  IF c='0' THEN HALT END;
END ReadPuin;

PROCEDURE PROC0;
BEGIN Write('0') END PROC0;

BEGIN (* main program actions *)
  PROC0;
  AddShutDowner(PROC9);  
  ReadPuin(ch);
  Program.NormalExit;
END MOD0.

(*-------------------------end of the main program-----------*)

(*-------the implementations of used modules----------------*)

(*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
IMPLEMENTATION MODULE  MOD1;
IMPORT Program, Terminal;

(*==========an internal module================*)
MODULE  MOD3;
IMPORT Program,Terminal;
EXPORT QUALIFIED PROC3,PROC8;

PROCEDURE PROC3;
BEGIN Terminal.Write('3') END PROC3;

PROCEDURE PROC8;
BEGIN Terminal.Write('8') END PROC8;

BEGIN(* actions of module MOD3 *)
  PROC3;
  Program.AddShutDowner(PROC8);
END MOD3;

(*========end of internal module=====================*)

PROCEDURE PROC1;
BEGIN Terminal.Write('1') END PROC1;

BEGIN(* initial actions of module MOD1 *)
  PROC1;
END MOD1.

(*++++++++++++++++++++++++++++++++++++++++++++++++++++*)

(*++++++++++++++++++++++++++++++++++++++++++++++++++++*)
IMPLEMENTATION MODULE  MOD2;
IMPORT Program;
IMPORT Terminal;

PROCEDURE PROC2;
BEGIN Terminal.Write('2') END PROC2;

PROCEDURE PROC7;
BEGIN Terminal.Write('7') END PROC7;

BEGIN(* actions of module MOD2 *)
  PROC2;
  Program.AddShutDowner(PROC7);
END MOD2.

(*++++++++++++++++++++++++++++++++++++++++++++++++++++++*)

(*++++++++++++++++++++++++++++++++++++++++++++++++++++++*)

IMPLEMENTATION MODULE Program;
IMPORT GEMX, GEMDOS, AESForms;
FROM SYSTEM IMPORT ADDRESS;
FROM Strings IMPORT String, Concat;
FROM M2Conversions IMPORT ConvertInteger, ConvertAddrHex;

CONST lastShutDowner = 128;
(* in definition module: TYPE ShutDownProc = PROC; *)
VAR nrShutDowners : INTEGER;
    ShutDowner    : ARRAY[0..lastShutDowner] OF ShutDownProc;

PROCEDURE RunTimeErrorHandler;
VAR s,s1,s2,dest : String;

BEGIN
  SimpleAlert(
  'RUNTIME ERROR!|Re-boot if no further|messages appear');
  ConvertInteger(GEMX.ErrorContext.Error,4,s);
  ConvertAddrHex(GEMX.ErrorContext.PC 
                 - ADDRESS(RunTimeErrorHandler), 8,s2);
  Concat('ERROR # ',s,s1); 
  Concat(s1,' at Offset ',s); 
  Concat(s,s2,dest);
  FatalError(dest);
END RunTimeErrorHandler;

PROCEDURE Halt();
VAR dummy : BOOLEAN;
BEGIN
  dummy := GEMDOS.Term(0)
END Halt;

PROCEDURE AddShutDowner(formal : ShutDownProc);
BEGIN
  IF nrShutDowners > lastShutDowner THEN
    formal();
    FatalError(
    "Increase lastShutDowner in module 'Program' ");
  END;
  ShutDowner[nrShutDowners] := formal;
  INC(nrShutDowners);
END AddShutDowner;

PROCEDURE NormalExit;
VAR i : INTEGER;
BEGIN
  FOR i := 0 TO nrShutDowners-1 DO
    ShutDowner[nrShutDowners-1-i]()
  END;
  Halt;
END NormalExit;

PROCEDURE FatalError(VAR s : ARRAY OF CHAR);
VAR dummy : INTEGER; as : String;
BEGIN
  Concat("[3][FATAL ERROR|",s,as); 
  Concat(as,"][BAH]",as);
  dummy := AESForms.FormAlert(1,as);
  NormalExit;
END FatalError;

PROCEDURE SimpleAlert(VAR s : ARRAY OF CHAR);
VAR as : String; dummy : INTEGER;
BEGIN
  Concat("[1][",s,as); 
  Concat(as,"][BAH]",as);
  dummy := AESForms.FormAlert(1,as);
END SimpleAlert;

BEGIN
  nrShutDowners := 0;
  GEMX.ErrorProcessor := RunTimeErrorHandler;
END Program.
(*++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
As you can see,  the module 'Program' contains two statements  to 
execute,  in  this  context also called  'initialising  actions'. 
About  the features of this module I will talk later on  in  this 
article.
The  import in the main program (module MOD0) of  'Program'  will 
result  in  the  execution  of  the  initialising  statements  of 
'Program'.  The import of 'MOD1' will also result in execution of 
its  initialising statements.  But module MOD1 also  contains  in 
itself  a module 'MOD3',  so logically there is a need  that  the 
actions belonging to the module 'MOD3' are done before 'MOD1'  is 
activated. This is indeed done.
The  first number on the screen that appears  is  3.  Immediately 
afterwards  the procedure PROC8 is the first to be  administrated 
by the 'Program.AddShutDowner' procedure.
The next initialising actions to be done are in MOD1,  and indeed 
the next number on the screen is 1.

The 'flow of control' goes now back to the module MOD0 where  the 
next  import  that  is to be done is that  of  module  MOD2.  The 
action in 'MOD2' takes care of the writing of the number 2 to the 
screen.  Also  PROC7  is  put  in  he  list  of  terminal  action 
procedures. (this is the second procedure that is put in the list 
of procedures to execute when the program terminates).
Next  a  return  to the MOD0 module takes  place  where  all  the 
imports  are done now ( except 'Terminal',  but there is no  need 
to  talk about that one in this context ) and its  own  'actions' 
can take place,  the first action being the writing of the number 
0 to the screen. A third shutdown procedure is added next.
The 'Read(ch);' gives you as user the breathing space to see what 
has happened on the screen untill now.
The  last  action  of  the main module MOD0  is  a  call  to  the 
procedure  'Program.NormalExit'.  And be awake!  Just before  the 
screen is cleaned and whipped away,  the numbers 9,  7 and 8  are 
displayed.  This  result  is only possible  when  the  procedures 
PROC9, PROC7 and PROC8 are executed.

When  you  run  the program for a second time and  you  type  the 
number  zero  (0) (after 3120 is displayed on the  text  screen), 
then  a  runtime  error  is forced by  way  of  the  Modula  HALT 
statement; an alert box appears with the text 'RUNTIME ERROR..'.
So you see,  our own runtime handler is activated and we are  not 
dependent  any more on the standard runtime error  handling  that 
TDI has suppied with the Modula runtime system (module GEMX).  
After answering the alert button a call to the Program.NormalExit 
is  done.   So  the  result  is  that,   although  the  statement 
'Program.NormalExit;'  in  module  MOD0  is  not  executed,   the 
RunTimeErrorHandler takes care of all the shut-down actions. I am 
very glad with this result.
When we take a closer look at the module 'Program' there are some 
remarkable things as there are:

1. there is a list of 128 shut-down procedures possible. ( enough 
for most applications),
2.  a procedure called AddShutDowner puts these procedures in the 
table,

3.  the  procedure  NormalExit is the executer of  the  shut-down 
procedures.  He starts with the last added procedure in the table 
and  ends  with the first added procedure.  Take a good  look  at 
this;  its is indeed the logical order of succession we need;  in 
this demo program the order is PROC2, PROC0, PROC9 and PROC7.
4.  One  of  the initial actions of the module 'Program'  is  the 
installation    of   our   own   RunTimeErrorHandler    as    the 
GEMX.ErrorProcessor.  So you keep things in your own hand in case 
of error conditions.
5.  The Program.Halt procedure terminates the program with a call 
to  GEMDOS.TERM(0).  In  most cases this is the best  way  for  a 
program to extinguish itself.

POST-SCRIPTUM

If  you handle things according to the above listed  program  you 
should consider the following:
1.  For a normal program ending let the last executable statement 
in the main module be a call to Program.NormalExit.
2.  If you want also a correct termination of the program in case 
of   runtime   errors   you   should   install   an   own    made 
GEMX.Errorprocessor  (that  mostly  will also do a  call  to  the 
Program.NormalExit.
3.  Think  long  and  deeply about the  creative  uses  that  are 
possible with the HALT statement now that we are able to vary the 
error respons of the program.
4.  Keep track of the order of precedence of the initial  actions 
of imported modules. When you know how the start-up goes than you 
can have an idea of how to shut-down.

© Stichting Modula Nederland
Bakkersteeg 9A
2311 RH LEIDEN
The Netherlands

Disclaimer
The text of the articles is identical to the originals like they appeared in old ST NEWS issues. Please take into consideration that the author(s) was (were) a lot younger and less responsible back then. So bad jokes, bad English, youthful arrogance, insults, bravura, over-crediting and tastelessness should be taken with at least a grain of salt. Any contact and/or payment information, as well as deadlines/release dates of any kind should be regarded as outdated. Due to the fact that these pages are not actually contained in an Atari executable here, references to scroll texts, featured demo screens and hidden articles may also be irrelevant.