\documentclass[a4paper]{article} \usepackage[utf-8]{inputenc} \usepackage[german]{babel} \usepackage{url} \usepackage{alltt} \usepackage{multicol} \title{Throw \& Catch Exception} \ifx\shorttitle\undefined\else \shorttitle{Catch \& Throw} \fi \author{Michael Kalus} \begin{document} \maketitle Wenn es darum geht, in ANS--Forth Ausnahmen des Programmablaufs (exception) zu behandeln, braucht man eine definierte Methode. Sie soll nicht nur zur Fehlerbehandlung springen, sondern auch die Stacks, besonders den Returnstack, sauber aufgeräumt hinterlassen. So eine Methode ist \texttt{throw} und \texttt{catch}. Damit wurde ein Multilevel-Exit verwirklicht, ein Konzept, das in C als setjmp() und longjmp(), und in LISP's ebenfalls als \texttt{CATCH} und \texttt{THROW} erfolgreich ist. Im Folgenden wird zunächst das übliche exit, dann das multilevel exit näher betrachtet. Sodann wird das Konzept des \texttt{throw} und \texttt{catch} beschrieben. Schließlich wird der Umgang mit den zugehörigen Meldungen, den exceptions, erläutert. \begin{multicols}{2} \section{One level Exit} Strukturiertes Programmieren in Forth erfordert bekanntlich keine besondere Mühe, weil Forth selbst schon so angelegt ist. Jede Routine (word) wird über ein exit verlassen. Und der exit führt zum inneren Interpreter, der über den IP (instruction pointer) die nächste Routine ausführen wird (next). Der übliche Weg ist also immer so, dass das Semikolon den regulären exit herstellt ( 1). Auch Verzweigungen ändern daran nichts ( 2). Selbst eine Konstruktion mit multiplen exits fällt auf die gleiche Ebene zurück wie das reguläre Wortende ( 3). Und auch im Fall, dass eine Schleife vorzeitig verlassen wird, landen wir im regulären Exit ( 4). \begin{verbatim} ( 1) : spam ( -- ) tuwas ; <-- Exit. ( 2) : spam1 ( -- ) tuwas0 0= IF tuwas1 THEN ; <-- Exit. ( 3) : spam2 ( -- ) tuwas0 0= IF tuwas1 exit \ <-- ELSE tu-was-anderes THEN ; <-- Exit. ( 4) : sapm3 ( -- ) 10 0 DO i 5 = IF leave THEN LOOP ; <-- EXIT \end{verbatim} \section{Multilevel Exit} Nun kann es sein, dass, während ein Programm arbeitet, etliches auf dem Daten- und Returnstack aufgetürmt worden ist, und dann ein Fehler bzw.\ eine \emph{Ausnahme} (exception) auftritt. Alles, was noch auf den Stacks ist, kann nun unbrauchbar sein, z.~B.\ ein Stacküberlauf. Das soll aber nicht zum Systemabsturz führen, sondern das Programm muss möglichst unschädlich mit einer passenden Meldung zum Benutzer zurückkehren. Es muss also, unabhängig davon, wo Return- oder Datenstack gerade hingewachsen sind, zu einem definierten Grundzustand zurückgefunden werden. Klassischerweise genügte dazu noch das Abort, welches in die Hauptroutine namens QUIT zurücksprang, und dort auf neue Eingaben wartete --- das war aber kein reguläres exit, sondern ein Notausgang. Solche Notausgänge sind jedoch für komplexere Aufgaben nicht geeignet, da in den meisten Fällen weitergemacht werden muss im Programm, selbst dann, wenn eine Teilfunktion in eine Ausnahme läuft. Hier setzt das Konzept von \texttt{catch} und \texttt{throw} an. Es ist in der Lage, auch über mehrere Verschachtelungsstufen (nesting) hinweg zu einem sicheren vordefinierten Zustand zurückzukehren. So ein Sprung über mehrere reguläre exits hinweg wird multilevel exit genannt. Mitch Bradley hat um 1990 herum \texttt{catch} \& \texttt{throw} in Forth formuliert, und so den multilevel exit auch für uns realisiert. \begin{verbatim} : spam0 ... ... ... #exc throw ... ; : spam1 ... spam0 ... ; | : spam2 ... spam1 ... ; | ... V : spamx ... ['] sapm2 catch #exc-handling ; \end{verbatim} \section{Throw} Auch wenn \texttt{catch} und \texttt{throw} eigentlich nur im Zusammenhang gesehen werden können, ist das Konzept einfacher zu verstehen, wenn man sich zunächst auf \texttt{throw} konzentriert. Im Grunde ist \texttt{throw} nur eine Verzweigung im Programmfluss, wie das if auch. Aber während if immer nur innerhalb einer Routine verzweigt, findet \texttt{throw} auch durch mehrere Ebenen hindurch sein Ziel. Wie das if auch, verzweigt \texttt{throw} je nach flag auf dem Stack. Findet \texttt{throw} eine Null oben auf dem Datenstack, verzweigt es nicht. In allen anderen Fällen stellt \texttt{throw} die Weichen für den Sprung hin zu einer vorher vereinbarten Stelle. Diese Stelle wird gewöhnlich im \emph{Rahmen für die Behandlung von Ausnahmen} übergeben (catchframe). Durch diese Konstruktion ist also ein conditional multilevel exit möglich. \section{Catch} Damit das \texttt{throw} aber kein goto wird, sondern strukturiert bleibt, braucht es ein Gegenüber, welches den Ausnahmerahmen erzeugt. So wie das \texttt{then} dem \texttt{if} während der Compilation sagt, wo es hingeht, so legt auch das \texttt{catch} für das \texttt{throw} ein Sprungziel fest. Und es legt darüber hinaus noch den Rahmen an, mit dem auch die Stackzeiger restauriert werden können. So wird für eine sichere Landung in der Ausnahmebehandlung gesorgt. Es ist Sache des Programmierers, solch einen Rahmen zu definieren, mit dem Forth in einen abgesicherten Zustand zurückkehren kann, falls eine Ausnahmebedingung im System aufgetreten ist. Im ANS ist lediglich ein minimaler Rahmen (catchframe) vordefiniert. Darin werden neben dem Sprungziel auch die Zeigerpositionen für den Daten-- und den Returnstack angegeben, und es wird eine Ausnahmenummer (exception number, \verb|#exc|) übermittelt. Der Standard hat für eine ganze Reihe von Ausnahmezuständen des Forthsystems solche Nummern schon festgelegt. (Siehe Liste) \section{Throw ausprobieren} Wie immer wollen wir mit solchen Forthkonzepten auch spielen können. Klar geht das händisch, jedenfalls im Gforth. \begin{verbatim} -1 throw \end{verbatim} liefert uns eine Meldung und das ok wie immer. \begin{verbatim} *the terminal*:7: Aborted -1 throw ^^^^^ Backtrace: ok \end{verbatim} Hier führte die Fehlernummer -1 zum Neustart. Da es am Terminal eingegeben wurde, bleibt der Backtrace natürlich leer. Tritt dagegen die Ausnahme in einem Programm auf, wird ermittelt, wo das gewesen sein könnte. Hier ein simpler screenshot dazu. (Die :14: in dem Beispiel bedeutet das es die 14.\ Zeile Input am Terminal gewesen ist, welche den Fehler auslöste.) Jede andere Fehlernummer kann natürlich ebenso ausprobiert werden. \begin{verbatim} : spam -1 throw ; ok : blah spam ; ok : buh blah ; ok buh *the terminal*:14: Aborted buh ^^^ Backtrace: $1026590 throw $10265B0 spam $10265D0 blah \end{verbatim} Bis hierher haben wir \texttt{throw} einzeln für sich ausprobiert. Genau genommen hatte es aber schon ein \texttt{catch}, das den Rahmen dafür abgegeben hat. Nur wurde jenes \texttt{catch} ja bereits im Kern des Forthsystems angelegt. Daran ist sehr schön zu erkennen, das es bei so einem multilevel exit ja auch gar nicht darauf ankommt, wie das letztlich gemacht worden ist. Das \texttt{throw} findet auf jeden Fall spätestens auf der Systemebene einen \texttt{catch} frame und verhält sich entsprechend. So ist es möglich, die Standard--Ausnahmezustände des Systems für eigene Programme auszunutzen, ohne sich mit dem \texttt{catch} überhaupt näher befassen zu müssen. Daher kann auch das klassische abort oder das \verb|abort"| damit einfach formuliert werden. \begin{verbatim} : abort -1 throw ; : spam ( #exc -- ) abort" na so was!" ; \end{verbatim} \section{Catch ausprobieren} Das Gegenstück zum \texttt{throw} ist das \texttt{catch}. \texttt{catch} baut für das \texttt{throw} immer einen sichernden Ausnahmerahmen auf, bevor es das Forthwort ausführt, das absturzsicher gemacht werden soll. So ist dafür gesorgt, das unter unserem Programmwort immer das sichernde Netz des \texttt{catch} liegt. Und tritt die Ausnahme tatsächlich ein, geht es hinter dem \texttt{catch} weiter. Ansonsten geht alles so, als wäre \texttt{catch} nicht da gewesen. Wie das if und then können \texttt{catch} und trow ebenfalls verschachtelt angewendet werden. Erreicht wird der Sprung von \texttt{throw} hinter sein Gegenstück, das \texttt{catch}, über die Manipulation der execution tokens auf dem Returnstack. Die Fehlerbehandlung hingegen durch die Übergabe einer Ausnahmenummer \verb|#exc|. Die wird nach dem Sprung hinter das \texttt{catch} zuoberst auf dem Stack sein, und damit wird weiter gemacht. Im günstigsten Fall ist \verb|#exc| eine Null, und es gibt an dieser Stelle nichts weiter zu tun. Das folgende Beispiel dazu ist dem dpans entnommen. Weitere Beispiele finden sich im Gforth Manual. \begin{verbatim} : 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 ( -- x1 x2 ) 2DROP ." There was an exception" CR ELSE ." The character was " EMIT CR THEN ; : retry-it ( -- ) BEGIN 1 2 ['] do-it CATCH WHILE ( exception handling: ) ( -- x1 x2) 2DROP ." Exception, keep trying" CR REPEAT ( char ) ." The character was " EMIT CR ; \end{verbatim} Am \texttt{retry-it} ist schön zu sehen, dass die Ausnahmebehandlung auch darin bestehen kann, im Programm weiter zu machen. Solange man \emph{ausnahmsweise} das große Q drückt, erlaubt uns die Schleife im \texttt{retry-it}, weiterzumachen, bis wir irgend eine andere Taste erwischen. Dabei ist auch gut erkennbar, dass \texttt{throw} über drei Ebenen hinweg wieder hinter dem \texttt{catch} aufsetzt, also mit der Ausnahmenummer dort in die Schleife eintritt. Außerdem erkennt man, dass der Datenstack hinter dem \texttt{catch} derselbe ist wie vorher! Die beiden Einträge dort \texttt{( -- x1 x2 )} sind nach wie vor vorhanden. Denn das \texttt{do-it} ist ja gescheitert. Diese beiden nun wertlos gewordenen Daten müssen also durch unsere Ausnahmeregelung (exception handling) entsorgt werden. Als Beispiel mag auch dienen, wie im Gforth mit Dateien umgegangen werden sollte. \begin{verbatim} s" spam.in" r/o open-file throw Value fd-in \end{verbatim} Das \texttt{throw} verarbeitet zunächst die Ausnahmenummer *bevor* der file--handler als Value gespeichert werden kann. Das korrespondierende \texttt{catch} wird vom Systemkern gestellt. Übrigens sind \texttt{catch} und \texttt{throw} im Gforth defered words, und können durch eigene Prozeduren ersetzt werden. \section{Exception} Abgerundet wird das Conditional--Multilevel--Exit--Konzept für die Ausnahmebehandlung durch eine einfach zu handhabende Art, eigene Ausnahmenummern samt Meldung vergeben zu können. Dazu wird das Wort exception benutzt. Es fügt einen Meldungstext in die Ausnahmeliste ein und erzeugt eine fortlaufend absteigende Ausnahmenummer, im Gforth, angefangen bei -2050. \texttt{Throw} verhält sich damit grundlegend anders als \verb|abort"|, welches ja an den Ort seiner Definition gebunden war. Die Liste der Ausnahmemeldungen kann mit n .error inspiziert werden. Das errorlisting erzeugt eine Übersicht der selbst definierten und der im System schon vergebenen Meldungen. screenshot: \begin{verbatim} s" spam1" exception .s <1> -2050 ok s" spam2" exception .s <2> -2050 -2051 ok s" spam3" exception .s <3> -2050 -2051 -2052 ok ... -2052 throw *the terminal*:2: spam3 ... -2050 .error spam1 ok -7 .error Do-loops nested too deeply ok : errorlisting ( -- ) 0 next-exception @ do cr i . i .error loop ; \end{verbatim} Im Gforth compiliert exception die Meldungen in eine linked list und .error greift in diese Liste. Da es im ANS--Forth aber keine standardisierte Art und Weise gibt, wie Meldungen angelegt werden sollten, ist exception spezifisch für das Gforth. Eine funktional abgespeckte, aber von der Schnittstelle her kompatible Implementation gibt es übrigens in compat.zip der TU Wien bei Anton Ertl (1). Damit kann man Programme, die exception benutzen, auf allen Standard-Systemen laufen lassen, auch in kleineren Microcontroller--Boards, die keine eigenen Fehler--Listen enthalten. So, und nun noch viel Vergnügen beim Erkunden. \end{multicols} Quellen: Crook, Ertl, Kuehling, Paysan, Wilke. Gforth Manual. https://www.complang.tuwien.ac.at/forth/gforth/Docs-html/ exceptions (ANS compatible) http://www.complang.tuwien.ac.at/forth/compat.zip. dpans. A.9. The optional Exception word set. Table 9.2 - THROW code assignments. http://www.taygeta.com/forth/dpans1.htm Milendorf, M. CATCH and THROW. Sun Microsystems, Inc. Sala, F. 2003. Catch und Throw. In: Forth--Magazin \emph{Vierte Dimension}, Heft 4/2003, Seite 24. --- dpANS: \begin{verbatim} Table 9.2 - THROW code assignments (System predefined) Code Reserved for ---- ------------ -1 ABORT -2 ABORT" -3 stack overflow -4 stack underflow -5 return stack overflow -6 return stack underflow -7 do-loops nested too deeply during execution -8 dictionary overflow -9 invalid memory address -10 division by zero -11 result out of range -12 argument type mismatch -13 undefined word -14 interpreting a compile-only word -15 invalid FORGET -16 attempt to use zero-length string as a name -17 pictured numeric output string overflow -18 parsed string overflow -19 definition name too long -20 write to a read-only location -21 unsupported operation (e.g., AT-XY on a too-dumb terminal) -22 control structure mismatch -23 address alignment exception -24 invalid numeric argument -25 return stack imbalance -26 loop parameters unavailable -27 invalid recursion -28 user interrupt -29 compiler nesting -30 obsolescent feature -31 >BODY used on non-CREATEd definition -32 invalid name argument (e.g., TO xxx) -33 block read exception -34 block write exception -35 invalid block number -36 invalid file position -37 file I/O exception -38 non-existent file -39 unexpected end of file -40 invalid BASE for floating point conversion -41 loss of precision -42 floating-point divide by zero -43 floating-point result out of range -44 floating-point stack overflow -45 floating-point stack underflow -46 floating-point invalid argument -47 compilation word list deleted -48 invalid POSTPONE -49 search-order overflow -50 search-order underflow -51 compilation word list changed -52 control-flow stack overflow -53 exception stack overflow -54 floating-point underflow -55 floating-point unidentified fault -56 QUIT -57 exception in sending or receiving a character -58 [IF], [ELSE], or [THEN] exception \end{verbatim} \end{document}