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.