chiark / gitweb /
precomp: New directory for precomputed files.
[mLib] / utils / exc.h
1 /* -*-c-*-
2  *
3  * Structured exception handling in C
4  *
5  * (c) 1998 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the mLib utilities library.
11  *
12  * mLib is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU Library General Public License as
14  * published by the Free Software Foundation; either version 2 of the
15  * License, or (at your option) any later version.
16  *
17  * mLib is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU Library General Public License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public
23  * License along with mLib; if not, write to the Free
24  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
25  * MA 02111-1307, USA.
26  */
27
28 #ifndef MLIB_EXC_H
29 #define MLIB_EXC_H
30
31 #ifdef __cplusplus
32   extern "C" {
33 #endif
34
35 #include <setjmp.h>
36
37 /*----- Quick documentation -----------------------------------------------*
38  *
39  * This header file provides some exception handling facilities in C
40  * programs.  It modifies the syntax of the language slightly, using the
41  * preprocessor.
42  *
43  * The `throw' expression returns no value.  It has the syntax:
44  *
45  *   THROW ( expr , expr )
46  *
47  * The first expression must have type compatible with unsigned integer; it
48  * identifies an `exception type'.  The second must have type compatible
49  * with pointer to void; it contains the `exception data'.  Control is
50  * passed to the current exception handler.
51  *
52  * The `RETHROW' expression, valid only within an exception handler, causes
53  * the current exception to be thrown again.
54  *
55  * A `try' statement has the syntax:
56  *
57  *   TRY stat CATCH stat END_TRY;
58  *
59  * The first statement is called the `test'; the second is the `handler'.
60  * During execution of the test, the handler is added to a stack of
61  * active exception handlers; the topmost handler on this stack is called
62  * the `current' handler.  When execution of the test completes, the
63  * corresponding handler is removed from the stack.
64  *
65  * The test statement may complete in one of these ways:
66  *
67  *   * Normal completion -- control reaches the end of the statement
68  *     normally.
69  *
70  *   * Throwing an exception -- an exception is thrown when the handler is
71  *     the current exception handler.
72  *
73  *   * By executing a `break' statement.
74  *
75  *   * By executing the expression `EXIT_TRY' and transferring control to
76  *     a point outside the entire `try' statement (e.g., executing a `goto'
77  *     or `return' statement).
78  *
79  * Any other attempt to leave the test causes undefined behaviour.
80  *
81  * If an exception is thrown while the handler is the current exception
82  * handler, it is given control.  The variables `exc_type' and `exc_val'
83  * denote the exception type and value respectively -- they are passed
84  * unchanged from the `throw' expression which caused the exception.
85  * A handler is deactivated before it is invoked; if it causes an
86  * exception to be thrown (and does not contain a nested `try' statement)
87  * control will be passed to an earlier active handler.
88  *
89  * Control is passed to handlers using the `longjmp' function.
90  *
91  * Example:
92  *
93  *   TRY {
94  *     ... something dangerous ...
95  *   } CATCH switch (exc_type) {
96  *     case EXC_INTERESTING:
97  *       ... handle exception ...
98  *       break;
99  *     default:
100  *       ... do tidying up ...
101  *       RETHROW;
102  *   } END_TRY;
103  */
104
105 /*----- Exception type allocation -----------------------------------------*
106  *
107  * Nobody allocates exception types, so we'll just have to try to get along
108  * without too many collisions.  An exception type is an unsigned long,
109  * which gives us four bytes.  The top two bytes identify the library which
110  * `owns' the exception, with special values zero meaning `defined as part
111  * of the system' and 0xFFFF providing a shared space of types which can
112  * be used by anyone as long as they don't get seen by anyone else.
113  *
114  * The lower byte pair encodes a type number, and a value which defines
115  * the type of the value field (see below).
116  */
117
118 /* --- Type of an exception --- */
119
120 typedef unsigned long exc_extype;
121
122 /* --- Build a byte pair from two characters --- *
123  *
124  * Note the icky casting to handle signed chars.
125  */
126
127 #define EXC_PAIR(x, y) (((unsigned long)(unsigned char)(x) << 8) |      \
128                         (unsigned long)(unsigned char)(y))
129
130 /* --- Allocate an exception number --- */
131
132 #define EXC_ALLOC(owner, type) (((unsigned long)(owner) << 16) |        \
133                                 (unsigned long)(type))
134
135 /* --- Special owner codes --- */
136
137 #define EXC_GLOBAL 0u                   /* The global space defined here */
138 #define EXC_SHARED 0xFFFFu              /* The shared space for everyone */
139 #define EXC_MLIB EXC_PAIR('m', 'L')     /* Space for mLib exceptions */
140
141 /*----- Exception values --------------------------------------------------*
142  *
143  * Exception values can have several different types.  This is a mess, and
144  * C doesn't handle it too well, but we can try.  I'll encode the value type
145  * as part of the exception type, in the top bits of the bottom byte.  Messy?
146  * You betcha.
147  */
148
149 /* --- Encoding a value type in an extype --- */
150
151 #define EXC_TYPECODE(t, w) (((w) & ~0xC0u) | ((t) & 0xC0u))
152
153 /* --- The various value types --- */
154
155 #define EXC_NOVAL 0x00u                 /* No interesting value */
156 #define EXC_INTVAL 0x40u                /* Integer value */
157 #define EXC_PTRVAL 0x80u                /* Arbitrary pointer value */
158 #define EXC_STRVAL 0xC0u                /* Pointer to character string */
159
160 /* --- Allocating exceptions with appropriate types --- */
161
162 #define EXC_ALLOCN(o, t) EXC_TYPECODE(EXC_NOVAL,  EXC_ALLOC(o, t))
163 #define EXC_ALLOCI(o, t) EXC_TYPECODE(EXC_INTVAL, EXC_ALLOC(o, t))
164 #define EXC_ALLOCP(o, t) EXC_TYPECODE(EXC_PTRVAL, EXC_ALLOC(o, t))
165 #define EXC_ALLOCS(o, t) EXC_TYPECODE(EXC_STRVAL, EXC_ALLOC(o, t))
166
167 /* --- A union representing the type --- */
168
169 typedef union exc_exval {
170   int i;
171   void *p;
172   char *s;
173 } exc_exval;
174
175 /*----- Predefined exceptions ---------------------------------------------*/
176
177 /* --- @EXC_NOMEM@ --- *
178  *
179  * Value:       ---
180  *
181  * Meaning:     An attempt to allocate memory failed.
182  */
183
184 #define EXC_NOMEM EXC_ALLOCN(EXC_GLOBAL, 0u)
185
186 /* --- @EXC_ERRNO@ --- *
187  *
188  * Value:       @int errno@ = the error raised
189  *
190  * Meaning:     Some kind of OS error occurred.
191  */
192
193 #define EXC_ERRNO EXC_ALLOCI(EXC_GLOBAL, 1u)
194
195 /* --- @EXC_OSERROR@ --- *
196  *
197  * Value:       @os_error *e@ = pointer to error block
198  *
199  * Meaning:     For RISC OS programmers only: alternative way of propagating
200  *              errors.
201  */
202
203 #define EXC_OSERROR EXC_ALLOCP(EXC_GLOBAL, 1u)
204
205 /* --- @EXC_SIGNAL@ --- *
206  *
207  * Value:       @int sig@ = signal number
208  *
209  * Meaning:     Report the raising of a signal.
210  */
211
212 #define EXC_SIGNAL EXC_ALLOCI(EXC_GLOBAL, 2u)
213
214 /* --- @EXC_FAIL@ --- *
215  *
216  * Value:       @const char *p@ = pointer to expanatory string
217  *
218  * Meaning:     Miscellaneous error.
219  */
220
221 #define EXC_FAIL EXC_ALLOCS(EXC_GLOBAL, 0xFFu)
222
223 /*----- An exception handler block ----------------------------------------*/
224
225 /* --- Try to think of this as being opaque --- */
226
227 typedef struct __exc_hnd {
228   struct __exc_hnd *next;               /* Pointer to next record down */
229   exc_extype type;                      /* Type of this exception */
230   exc_exval val;                        /* Value of this exception */
231   jmp_buf buf;                          /* Jump buffer when exceptions hit */
232 } __exc_hnd;
233
234 /*----- Global variables --------------------------------------------------*/
235
236 extern __exc_hnd *__exc_list;           /* List of active handlers */
237
238 /*----- Macros ------------------------------------------------------------*/
239
240 /* --- References to current exception type and value --- */
241
242 #define exc_type (__exc_ec.type)
243 #define exc_val (__exc_ec.val)
244 #define exc_i (__exc_ec.val.i)
245 #define exc_p (__exc_ec.val.p)
246 #define exc_s (__exc_ec.val.s)
247
248 /* --- How it actually works --- *
249  *
250  * A `try' block is contained within a block which provides an exception
251  * handler buffer in automatic storage.  This block is a loop, to allow
252  * `break' to escape from it.  It adds the handler buffer to the top of a
253  * list, and does a `setjmp' to allow a return here following an exception.
254  * The `setjmp' returns zero for the `try' section, and nonzero if there's
255  * an exception to `catch'.  It looks a little like this:
256  *
257  *   do {
258  *     __exc_hnd h;
259  *     add_handler(&h);
260  *     if (!setjmp(h.buf)) {
261  *       do <try code> while (0);
262  *       remove_handler(&h);
263  *     } else
264  *       <catch code>
265  *   } while (0)
266  *
267  * Everything else is ugly hacking to make things work.
268  */
269
270 /* --- Trying things which may cause exceptions --- */
271
272 #define TRY do {                                                        \
273   volatile __exc_hnd __exc_ec;                                          \
274   __exc_ec.next = __exc_list;                                           \
275   __exc_list = (__exc_hnd *)&__exc_ec;                                  \
276   if (!setjmp(*(jmp_buf *)&__exc_ec.buf /* very nasty! */ )) { do
277
278 #define EXIT_TRY do __exc_list = __exc_ec.next; while (0)
279 #define CATCH while (0); EXIT_TRY; } else
280
281 #define END_TRY } while (0)
282
283 /* --- Raising exceptions --- */
284
285 #define THROW __exc_throw
286 #define RETHROW __exc_rethrow(__exc_ec.type, __exc_ec.val)
287
288 /*----- Functions ---------------------------------------------------------*/
289
290 /* --- @exc_uncaught@ --- *
291  *
292  * Arguments:   @void (*proc)(exc_extype type, exc_exval val) = new handler
293  *
294  * Returns:     Pointer to the old handler value.
295  *
296  * Use:         Sets the handler for uncaught exceptions.
297  */
298
299 typedef void (*exc__uncaught)(exc_extype /*type*/, exc_exval /*val*/);
300 extern exc__uncaught exc_uncaught(exc__uncaught /*proc*/);
301
302 /* --- @__exc_throw@ --- *
303  *
304  * Arguments:   @exc_extype type@ = type of exception to throw
305  *
306  * Returns:     Doesn't
307  *
308  * Use:         NOT FOR USER CONSUMPTION.  Reads an appropriate exception
309  *              value and throws an exception.
310  */
311
312 extern void __exc_throw(exc_extype /*type*/, ...);
313
314 /* --- @__exc_rethrow@ --- *
315  *
316  * Arguments:   @exc_extype type@ = type of exception to throw
317  *              @exc_exval val@ = value of exception to throw
318  *
319  * Returns:     Doesn't
320  *
321  * Use:         NOT FOR USER CONSUMPTION.  Does the donkey-work of raising
322  *              an exception.
323  */
324
325 extern void __exc_rethrow(exc_extype /*type*/, exc_exval /*val*/);
326
327 /*----- That's all, folks -------------------------------------------------*/
328
329 #ifdef __cplusplus
330   }
331 #endif
332
333 #endif