Die F68KANS Fehlerbehandlung



Die Fehlerbehandlung in F68KANS geschieht nach dem aktuellen Stand der 
ANSI-Forth Spezifikation.

Das dort vorschriebene Verfahren sttzt sich auf die beiden Worte CATCH und 
THROW und einen ganzen Satz vordefinierter Fehlernummern.

Diese beiden Worte werden durch EXCEPTION Wortsatz definiert. Bereits im CORE 
Wordsatz sind die beiden traditionellen Fehlerworte ABORT und ABORT" definiert.

	ABORT 	( i*x -- )( R: j*x -- )
	ABORT" 	Compilation: ( "ccc<quote>" -- )
		Execution: ( i*x xj --  | i*x )( R: j*x --  | j*x )

Das Standarddokument empfiehlt die Implementation dieser Worte mittels des 
EXCEPTION Wordsatzes, also mit CATCH und THROW. Genau dies ist in F68KANS 
auch verwirklicht.


Anwendung

Die Anwendung von ABORT und ABORT" ist althergebracht und nicht besonders 
schwierig zu durchschauen. Sie kehren eben einfach in den Interpreter zurck. 
Eigentlich kann dabei kaum von einer Fehlerbehandlung gesprochen werden, denn 
bezglich des Fehlers wird gar nichts unternommen.
Anders bei CATCH und THROW. Deren Anwendung ist rein mit Worten kaum 
vernnftig zu beschreiben. Deshalb soll das Beispiel aus dem Standarddokument 
weiterhelfen.


( EXCEPTION tests )
( JPS, 18apr93 )
( taken from the dpANS5 document )

: could-fail ( -- char )
	KEY DUP [CHAR] Q = IF 1 THROW THEN ;
	
: do-it ( a b -- c)
	2DROP could-fail ;
	
: try-it ( --)
	1 2 ['] do-it CATCH
	IF		2DROP ." There was an exception!" 	
	ELSE	." The character was " EMIT
	THEN CR ; 	



Hierbei ist could-fail das Wort, das den Fehler auslst (allgemeiner wird nicht von 
einem Fehler, sondern von einer Ausnahmesituation oder Exception gesprochen). Die 
Ausnahme besteht hier darin, da der Benutzer "Q" gedrckt hat. Darufhin wird ein 
THROW mit der 1 als Fehlercode ausgelst. Dies bewirkt, da jede weitere 
Bearbeitung abgebrochen wird. Das Programm wird unmittelbar hinter CATCH in 
try-it, das somit den Fehler aufgefangen hat, fortgesetzt. Es wird auf einen von Null 
verschiedenen Fehlercode geprft und entsprechend reagiert. Wichtig dabei ist, da 
in dem Fall, da could-fail gar kein THROW auslst und somit auch der 'normale' 
Programmflu irgendwann wieder in try-it bei CATCH ankommt, dort automatisch ein 
Pseudo-Fehlercode 0 erzeugt wird. Das signalisiert dem folgenden Code: "Es ist kein 
Fehler aufgetreten!"
Wichtig ist noch, da im Fehlerfall, also dann, wenn THROW ausgelst wurde, die 
Stackhhe wieder auf den Wert vor CATCH zurckgesetzt wird. Deshalb ist auch bei 
der Fehlerbehandlung in try-it ein 2DROP notwendig, da vor CATCH zwei Werte (1,2) 
auf dem Stack abgelegt worden sind. Ohne THROW, also im ungestrten 
Programmverlauf, sind diese beiden von do-it wieder entfernt worden.
Aber Vorsicht: nur die Stackhhe wird wiederhergestellt!! Nicht der Inhalt! Wenn man 
also 2DROP durch '. .' ersetzt, sollte man nicht mehr '2 1' erwarten.


Wozu ist das alles gut?

Nun, es ist mglich geworden, Fehler im warsten Sinne des Wortes abzufangen und 
damit auch Funktionen zu benutzen, die unter anderen Umstnden entweder nur 
durch umschreiben oder eben gar benutzbar gewesen wren, da diese Funktionen 
eine Ausnahmebehandlung erfordern.
Funktionen, die das klassische ABORT oder ABORT" benutzen, das also nicht als -1 
THROW bzw. -2 THROW implementiert ist, sind fr Applikationen nicht zu 
gebrauchen, denn was soll der Anwender im Interpreter.
Funktionen, die eine selbstgestrickte Ausnahmebehandlung benutzen, sind nur mit 
einem entsprechenden Umfeld zu gebrauchen.
Funktionen, die die Fehlervektoren des Systems verbiegen, sind damit sofort auch 
systemabhngig.

Eine Funktion die eine Ausnahme mit THROW auslst, signalisiert zunchst einmal 
das Auftreten einer Ausnahme. Wie damit umzugehen ist, bestimmt der 
entsprechende Fnger. Und das gilt sogar fr Systemfehler!

Ein Beispiel: in meiner eigenen Praxis ist es nicht nur einmal vorgekommen, da der 
Forth-Interpreter nicht Forth-Quelltext, sondern irgendetwas anderes interpretieren 
sollte. Dazu war es dann immer notwendig, auf unbekannte Worte zu reagieren. D.h., 
wenn der Interpreter im Vokabular nicht fndig wird, mu etwas besonderes 
geschehen (im einfachsten Fall gar nichts). Das ist aber ohne ein Forth-System, 
dessen interne Fehlerbehandlung nicht auf CATCH und THROW basiert, gar nicht so 
einfach, denn normalerweise reagiert der Interpreter ja auf unbekannte Worte mit 
einem 'heah?' oder 'unknown!' oder sonstwas und bleibt stehen (ABORT!).
Nicht jedoch, wenn man sebst den Fehler abfngt!

Angenommen, das Wort, das den Eingabefile liest und interpretiert heie 
PROCESS-INFILE. PROCESS-FILE verwendet den gewhnlichen Forth-Interpreter 
INTERPRET oder davon abgeleitete Worte wie INCLUDE-FILE. Weiter angenommen, 
man hat ein Wort namens PROCESS-UNKNOWN, mit dem man unbekannte Worte 
bearbeiten kann. Dann knnte man folgendes schreiben:


: MAIN ( -- )
	... 	( File ffnen etc. )
	BEGIN
		['] PROCESS-INFILE CATCH DUP
	WHILE
		[ DECIMAL ] -13 =		( ANSI Fehlernr.: undefined word )
		IF  PROCESS-UNKNOWN	( bearbeiten )
		ELSE  THROW		( anderer Fehler, weiterwerfen )
		THEN			
	REPEAT 		( File weiter interpretieren )
	DROP
	...	( File schlieen etc. )
	;



Da meine bisherigen Forthsysteme nicht ber ein CATCH/THROW-System verfgten, 
gab es an dieser Stelle immer anstelle dieses einfachen Codes mchtig rger.
Nur um Ihnen eine Vorstellung zu geben:

	- alten Fehlervektor sichern
	- neuen Fehlervektor installieren
		- der mute den alten ausfhren knnen
		- ggf. den alten wieder restaurieren
	- File interpretieren
	- alten Fehlervektor restaurieren
	- hoffen, da kein anderes Wort am Fehlervektor dreht

Dabei war immer nachzuforschen, wie denn nun der Fehlervektor eigentlich hie 
(systemabhngig!!), und was eine Funktion, die in einem Fehlervektor steht, denn fr 
Anforderungen erfllen mu, ...


Probleme?

Ich mchte trotz aller Pracht darauf hinweisen, da auch der CATCH/THROW-Ansatz 
noch gewisse Probleme mit sich bringt, die auch in dem obigen Beispiel sichtbar 
werden (war hat's gesehen?).
Woher wei PROCESS-UNKNOWN, welcher String nicht gefunden wurde? Durch den 
Stack sicher nicht, denn der wird von THROW schon auf seine ursprngliche Hhe 
bei undefiniertem Inhalt gestutzt. Es gibt keinen ANSI-legalen Weg, 
PROCESS-UNKNOWN den String direkt mitzuteilen. Als einzige Chance bleibt hier, 
im Eingabestrom das letzte Wort noch einmal herauszusuchen. Oder man wei, wo 
der Interpreter seine aus dem Eingabestrom gefilterten Strings ablegt. Aber das ist 
schon wieder systemabhngig, ein Attribut, da in der Anwendungsentwicklung auf 
ANSI-Niveau ja dringend zu vermeiden ist.

Dieses Problem kann umgangen werden, indem man sich in der Anwendung noch 
eine Ebene unter INTERPRET begibt und auf FIND und Konsorten aufbaut.

		
Implementation

Fr ABORT und ABORT" sind die beiden Fehlernummern -1 und -2 reserviert. Auch 
das Verhalten von ABORT und ABORT", nmlich lschen des Stacks und Rckkehr in 
den Interpreter QUIT, ist klar definiert, so da es lediglich bei der Behandlung des 
Strings bei ABORT" noch einen Implementationsspielraum gibt.


Wo steht das erste CATCH?

In der Interpreterschleife QUIT. Hier wird INTERPRET mit CATCH gestartet. Da 
QUIT immer die Wurzel der gesamten Aufrufhierachie ist, ist damit sichergestellt, 
da jedes THROW auch wieder aufgefangen wird.


Behandlung des Fehlers

Hinter CATCH in INTERPRET wird zur Auswertung der zurckgelieferten 
Fehlernummer eine Funktion aufgerufen, deren CFA in der USER-Variablen 
(errorhandler) abgelegt ist:


: QUIT ( -- )
	... 	( irgendwas )
	['] INTERPRET CATCH
	(errorhandler) @  EXECUTE
	...	( irgendwas )
;


Damit ist die gesamte Fehlerbehandlung durch das System trotz des eleganten 
CATCH/THROW-Ansatzes quasi fr den Fall der Flle zustzlich vollstndig 
austauschbar.
Standardmig steht in diesem Vektor die CFA des Wortes errorhandler:

	errorhandler ( errnum -- ? )

errorhandler hat die einfache Aufgabe, mit Hilfe der Fehlernummer passend zu 
reagieren. Probieren Sie es einfach einmal aus:

	-10 errorhandler


Meistens endet die Fehlerbehandlung mit einem Sprung zu QUIT. Problematisch ist 
nur die Handhabung von ABORT" und des in lteren Implementationen auftauchenden 
error", das fr einige Systemmeldungen sehr praktisch ist.

	error" 	Compilation: ( "ccc<quote>" -- )
		Execution: ( i*x c-addr xj --  | i*x )( R: j*x --  | j*x )
				
error" eignet sich hervorragend fr eine Fehlermeldung wie

	HAUMICHBLAU unknown!

wie Sie sie in hnlicher Form sicher schon gesehen haben. error" verarbeitet zwei 
Strings: einen festgelegten ("unknown!"), und einen, der zur Laufzeit als 
Counted-string bergeben wird (das nicht gefundene Wort).
Um diese Funktion und die von ABORT" verwirklichen zu knnen, haben sich 
ABORT", error" und errorhandler 'abgesprochen'.

Zunchst scheint es naheliegend zu sein, da ABORT" und error" wie in den alten 
Tagen ihre Strings auf das Terminal ausgeben und anschlieend mit einem passenden 
Fehlercode (-2 fr ABORT" und diverse fr die verschiedenen Einstze von error") 
THROW ausfhren. Dieses vorgehen erscheint logisch, hat aber den schweren 
Nachteil, da ein Teil der Fehlerbehandlung, das Ausgeben einer Meldung, schon 
ausgefhrt wird, bevor der eigentliche Fehlerhandler, der erst auf CATCH folgt, zum 
Zuge kommen kann. Damit wird es fr eine Applikation, die die Fehler abfngt, schon 
gar nicht mehr so einfach sein, die Bildschirmnachricht zu unterdrcken und 
stattdessen z.B. die Meldung in eine Datei umzuleiten.

Um dies zu bewerkstelligen, reduzieren ABORT" und error" ihre Aktivitten darauf, 
ihre jeweiligen Meldungen in Variablen abzulegen und dann THROW auszufhren. Die 
Absprache mit dem errorhandler besteht nun darin, da dieser die entsprechenden 
Variablen ebenfalls kennt und dann passend reagieren kann. Es handelt sich um die 
folgenden Variablen:

	abort"msg
	abort"cnt
	error"msg

ABORT" legt in abort"msg die Adresse des Strings ab. In abort"cnt wird die 
zugehrige Stringlnge gespeichert.
Stellt der errorhandler nun fest, da es sich bei der Fehlernummer um die -2 
handelt, so gibt er die Meldung, die er in den Variablen abort"msg und abort"cnt 
findet aus und verzweigt dann erst nach QUIT.
hnlich verhlt sich der errorhandler bei anderen Nummern, die mit error" erzeugt 
worden sind. Hier wird zustzlich noch der String ausgegeben, der als counted-string 
bei der Adresse, die in error"msg steht, abgelegt ist.
Fr error"msg steht keine eigene Variable fr die Lnge zur Verfgung, da alle in 
diesem Zusammenhang auftretenden Meldungen bereits als counted-strings vorliegen. 
Strings die hinter ABORT" und error" angegeben werden, also der feste Teil einer 
Meldung, fallen dagegen immer als Adresse und Lnge an, da ABORT" und error" 
mit Hilfe des Standardwortes S" implementiert worden sind.
					

Wenn Sie also nun Ihre eigene Fehlerbehandlung erstellen mssen, sollten Sie immer 
bedenken:

	- error" ist kein Standardwort (deshalb auch in Kleinschrift)
	- die Benutzung von abort"msg, abort"cnt und error"msg fllt unter den	
	Begriff Systemprogrammierung. Das ist implementattionsabhngig und hat in 
	Standardprogrammen nichts zu suchen! Im Rahmen des Standards drfen 
	NUR die Fehlernummern verwertete werden (jedenfalls was Systemfehler 
	angeht).
	
