/* signal.h L. Stewart June 2, 1982 9:25 AM From D. Swinehart's bcpl signaller package */ #define UNWIND 1 #define ANY 0 #define ERROR 2 #define minClientError 10 #define maxClientError 037777 /* Clients may assign SIGNAL values via compiled-in constants or any other scheme, if they fall within [minClientError..maxClientError]. If duplicate values result, caveat clientus. code() returns a new SIGNAL value outside this interval. To be sure each signal is distinct, make it a static and initialize it with sig = code(). */ #define REJECT 1 #define RESUME 2 #define RETRY 3 #define CONTINUE 4 /* CatchProc: TYPE = PROC[sig: SIGNAL, arg: UNSPECIFIED, seal: SEAL] RETURNS [control: ControlValue ] Catch phrases in this implementation are provided by actual procedures. sig is the signal value that was raised, and arg is another value supplied in the SIGNAL call. seal is described later. If the catch procedure returns RESUME, execution will continue after the SIGNAL call (there is no enforced analog of ERROR.) Returning REJECT causes the next catch phrase to be sought and invoked. Returning RETRY or CONTINUE causes the stack to be unwound, then a true (CONTINUE) or false (RETRY) return from the ENABLE for this seal (see below.) When the stack is unwound, catch procedures for UNWIND are called, as in Mesa. The control codes returned by UNWIND catch procedures are ignored. ANY will catch any signal, including an UNWIND. A catch phrase for ANY will not catch its own unwind -- same as Mesa. I don't know what happens when signals are raised within catch procedures, etc. */ struct Seal { int slauth; int slid; }; struct Seal1 { int slauth; int slid; int data[1]; }; #define lenSeal1 (sizeof(struct Seal1)/2) struct Seal2 { int slauth; int slid; int data[2]; }; #define lenSeal2 (sizeof(struct Seal2)/2) #define lenseal 4 /* The client must supply a pointer to a two-word (or larger) block as a seal. The signaller stores values in the seal that allows it to verify the seal's validity and identify the specific catch phrase instance later, when the signal is raised. This is done so that the client does not have to explicitly disable each ENABLE when leaving the scope of the ENABLE. Roughly speaking, the ENABLE is in effect until: The client calls DISABLE, specifying the same seal. The client calles ENABLE again, specifying the same seal. The client procedure containing the ENABLE returns. The seal is supplied to the catch procedure so that, if the client knows something about the seal that the signaller doesn't know (like what's in words 2..n, or where the seal is located within the local frame), the catch proc can gain some sort of access to the ENABLE's context. Oh, for true nested scopes! Catch phrase procs are called, until accepted, in reverse temporal order of ENABLE within still-valid frames. There can be several ENABLEs in effect for each local frame -- the frame orders must not be rearranged while the ENABLES are valid. When the client calls ENABLE, it returns false. Later, when a catch procedure corresponding to that ENABLE's seal returns CONTINUE, it appears to the client as if the same ENABLE has just returned true. If the catch procedure specifies RETRY, the client sees another false return from the ENABLE. So a typical use might be: struct seal myseal; int catcher(sig, code, seal) int sig, code; struct seal *sl; { if (code==unbound) return (CONTINUE); else return (REJECT); }; main() { if (enable(callfailed, catcher, &myseal)) return; xxx; }; Two situations to avoid: 1) Scope lasts too long. Suppose that procedure P enabled signal S using seal L, then called Q, which returned normally. Suppose that subsequently P is unprepared to handle S using that catch procedure. P must explicitly DISABLE L. 2) Invalid scope appears valid: Suppose that procedure P enabled signal S using seal L, did some work, then returned. Suppose further that procedure P is then called again, and its local frame ends up in the same place (likely, if P is called again by its previous caller.) Finally, suppose that P or one of its called procedures raises S before P reaches the enable of S sealed by L. The seal will still appear valid, and the catch procedure will be called, possibly at a time that P cannot deal with. So if you enable a signal after some point in your procedure that could cause it to be raised, DISABLE the seal on entry to the procedure. (Setting either word of the seal to zero will also disable it -- oh, for inlines!) */ /* procedures sigginit() Initialize the signaller package siginit(svec, sveclen, gvproc) svec is a block of storage of length sveclen bytes, it will be used for saving enable information. There should be a separate call on siginit from each process which uses signals. gvproc should be tghe same in each case. It is a procedure that the signaller will use to aquire the apropriate svec for the running process. int enable(s, p, sl) sl is a block of length lenseal for the signallers use. p is the procedure to be called when the signal is raised s is the signal value, aquired from code() perhaps. enable returns false on the initial call and on RETRY enable returns true on CONTINUE disable(sl) disable the signal corresponding to seal sl signal(s, c) raise signal s with argument c int code() acquire a unique signal value */