{Begin SubSec Changing and Restoring System State}
{Title Changing and Restoring System State}
{Text


{Note Apparently, there are still a lot of problems with the RESET*** functions; problems which became accentuated when people tried running programs under the process world.  Therefore, I will try to clean up the most obvious problems in this section, but I won't try  too hard ... there will probably be significant changes in some of these functions.}


In Interlisp, a computation can be interrupted/aborted at any point due to an error, or more forcefully, because a control-D was typed, causing return to the top level.  This situation creates problems for programs that need to perform a computation with the system in a "different state", e.g., different radix, input file, readtable, etc. but want to "protect" the calling environment, i.e., be able to restore the state when the computation has completed.  While program errors and control-E can be "caught" by errorsets, control-D is not.{foot
Note that the program could redefine control-D as a user interrupt ({PageRef Tag UserInterruptChars}), check for it, reenable it, and call {fn RESET} or something similar.
}{comment endfootnote}
Thus the system may be left in its changed state as a result of the computation being aborted.  The following functions address this problem.


Note that these functions do not and cannot handle the situation where their environment is exited via anything other than a normal return, an error, or a reset.  E.g. a {fn RETEVAL}, {fn RETFROM}, {fn RESUME}, etc., will never be seen.

{note how about a REVERT from a break??}


{FnDef {FnName RESETLST} {FnArgs FORM{sub 1} {ellipsis} FORM{sub N}}
{TYPE NOSPREAD NLAMBDA}
{Text
{fn RESETLST} evaluates its arguments in order, after setting up an {fn ERRORSET} so that any reset operations performed by {fn RESETSAVE} (see below) are restored when the forms have been evaluated (or an error occurs, or a control-D is typed).  If no error occurs, the value of {fn RESETLST} is the value of {arg FORM{sub N}}, otherwise {fn RESETLST} generates an error (after performing the necessary restorations).
{Note the Same Error?}
{fn RESETLST} compiles open.

{Note Are there any problems that occur when RESETLSTs are nested? ---mjs}
}}


{FnDef {FnName RESETSAVE} {FnArgs X Y}
{TYPE NOSPREAD NLAMBDA}
{Text
{fn RESETSAVE} is used within a call to {fn RESETLST} to change the system state by calling a function or setting a variable, while specifying how to restore the original system state when the {fn RESETLST} is exited (normally, or with an error or control-D).

If {arg X} is atomic, resets the top level value of {arg X} to the value of {arg Y}.  For example, {lisp (RESETSAVE LISPXHISTORY EDITHISTORY)} resets the value of {var LISPXHISTORY} to the value of {var EDITHISTORY}, and provides for the original value of {var LISPXHISTORY} to be restored when the {fn RESETLST} completes operation, (or an error occurs, or a control-D is typed).  This use is somewhat anachronistic in Interlisp-10 in that in a shallow bound system, it is sufficient to simply rebind the variable.  Furthermore, if there are any rebindings, the {fn RESETSAVE} will {it not} affect the most recent binding but will change only the top level value, and therefore probably not have the intended effect.

{note does this format have any use other than to reset Interlisp-D GLOBALVARS (since it doesn't matter if there are rebindings)?}


If {arg X} is not atomic, it is a form that is evaluated.
If {arg Y} is {lisp NIL}, {arg X} must return as its value its "former state", so that the effect of evaluating the form can be reversed, and the system state can be restored, by applying {fn CAR} of {arg X} to the value of {arg X}.  For example, {lisp (RESETSAVE (RADIX 8))} performs {lisp (RADIX 8)}, and provides for {fn RADIX} to be reset to its original value when the {fn RESETLST} completes by applying {fn RADIX} to the value returned by {lisp (RADIX 8)}.

In the special case that {fn CAR} of {arg X} is {fn SETQ}, the {fn SETQ} is transparent for the purposes of {fn RESETSAVE}, i.e. the user could also have written {lisp (RESETSAVE (SETQ X (RADIX 8)))}, and restoration would be performed by applying {fn RADIX}, not {fn SETQ}, to the previous value of {fn RADIX}.

If {arg Y} is not {lisp NIL}, it is evaluated (before {arg X}), and its {it value} is used as the restoring expression.  This is useful for functions which do not return their "previous setting".  For example,

{lispcode [RESETSAVE (SETBRK {ellipsis}) (LIST 'SETBRK (GETBRK]}

will restore the break characters by applying {fn SETBRK} to the value returned by {lisp (GETBRK)}, which was computed before the {lisp (SETBRK {ellipsis})} expression was evaluated.  Note that the restoration expression is still "evaluated" 
by {it applying} its {fn CAR} to its {fn CDR}.

{Begin Note}
Why say "applying its CAR to its CDR" ?? Is this important??  Isn't this just the same as EVALing it twice, EVALing (LIST(QUOTE SETBRK)(GETBRK] to get (SETBRK <whatever>) and EVALing that to do the restoration?  I can see why you want to look at the CAR of the first argument (to get RADIX or whatever), though it is a bit of a kludge.  Is something strange happening here, or is it just aukward wording?  ---mjs

answer...this IS important.  APPLY does not evaluate the individual args in the arg list given it (with eith lambda or nlambda fn), so when you apply the CAR to the CDR, the elements of the CDR are not evaluated at restoring time, unless the CAR is an nlambda that evals its arguments explicitly, like AND or PROGN.  This causes a lot of problems later, when using OLDVALUE (see below)

big question: how SHOULD this be done?  you need to use an APPLY above so that you can evaluate (LIST 'SETBLK (GETBLK)) to get (SETBLK <messy data>), and then "eval" that without trying to eval <messy data>.  But actually, constucting restoration expressions this way is a big crock anyway.  There must be a better interface to restoration expressions.  (perhaps we could think of a more general function, implement these crocks in terms of that fn, and gradually phase out the crocks.)
{End Note}


If {arg X} is {lisp NIL}, {arg Y} is still treated as a restoration expression.  Therefore,

{lispcode (RESETSAVE NIL (LIST 'CLOSEF FILE))}

will cause {lisp FILE} to be closed when the {fn RESETLST} that the {fn RESETSAVE} is under completes (or an error occurs or a control-D is typed).


Note:  {fn RESETSAVE} can be called when {it not} under a {fn RESETLST}.  In this case, the restoration will be performed at the next {lisp RESET}{index RESET FN}, i.e., control-D or call to {fn RESET}.  In other words, there is an "implicit" {fn RESETLST} at the top-level executive.

{fn RESETSAVE} compiles open.  Its value is not a "useful" quantity.

{Begin Note}
Why is RESETSAVE a NoSpread?  According to this write-up, it can only have at most two arguments.   Proposed change in documentation:  RESETSAVE is written as having two arguments X and Y, with 'CAR RESETX' -> X and 'CDR RESETX' -> Y and 'FORM' -> Y thoughout this description.  Is this reasonable, or is there something I don't understand that requires the current (unwieldy) description?

I made the above suggested change.
{End Note}
}}


{FnDef {FnName RESETVAR} {FnArgs VAR NEWVALUE FORM}
{Type NLAMBDA}
{Text
Simplified form of {fn RESETLST} and {fn RESETSAVE} for resetting and restoring global variables.{foot
Unnecessarily expensive in a shallow bound system as the variable can simply be rebound.
}{comment endfootnote}
Equivalent to {lisp (RESETLST (RESETSAVE {arg VAR} {arg NEWVALUE}) {arg FORM})}.{note not true, according to Ron}  For example, {lisp (RESETVAR LISPXHISTORY EDITHISTORY (FOO))} resets {var LISPXHISTORY} to the value of {var EDITHISTORY} while evaluating {lisp (FOO)}.  {fn RESETVAR} compiles open.  If no error occurs, its value is the value of {arg FORM}.
}}


{FnDef {FnName RESETVARS}
{FnArgs VARSLST E{SUB 1} E{SUB 2} {ellipsis} E{SUB N}}
{Type NOSPREAD NLAMBDA}
{Text
Similar to {fn PROG}, except the variables in {arg VARSLST} are global variables.  In a shallow bound system (Interlisp-10) {fn RESETVARS} and {fn PROG} are identical.{foot
Except that the compiler insures that variables bound in a {fn RESETVARS} are declared as {lisp SPECVARS}{index SPECVARS Var} (see {PageRef Tag SPECVARS}).
}{comment endfootnote}
In a deep bound system, each variable is "rebound" using {fn RESETSAVE}.
}}


{Begin Note}
doc??  'top-value cells'??.  Also, if 'The body is a PROGN body, not a PROG body', how are the variables specified? ---mjs

Date:  5 JAN 1982 2131-PST
From: KAPLAN
Subject: I added RESETTOPVALS in MACHINEINDEPENDENT

which is like RESETVARS except that it uniformly sets the top-value cell in both deep and shallow.  The body is a PROGN body, not a PROG body.
COmpiles open.

I also distributed this as appropriate in COMPILE, and put the macro in MACROS.
{End Note}


{fn RESETVARS}, like {fn GETATOMVAL} and {fn SETATOMVAL} ({PageRef Fn GETATOMVAL}), is provided to permit compatibility (i.e. transportablility) between a shallow bound and deep bound system with respect to conceptually global variables.


{FnDef {FnName RESETFORM}
{FnArgs RESETFORM FORM{SUB 1} FORM{SUB 2} {ellipsis} FORM{SUB N}}
{Type NOSPREAD NLAMBDA}
{Text
Simplified form of {fn RESETLST} and {fn RESETSAVE} for resetting a system state when the corresponding function returns as its value the "previous setting."  Equivalent to {lisp (RESETLST (RESETSAVE {arg RESETFORM}) {arg FORM{sub 1}} {arg FORM{sub 2}} {ellipsis} {arg FORM{sub N}})}.  For example, {lisp (RESETFORM (RADIX 8) (FOO))}.  {fn RESETFORM} compiles open.  If no error occurs, it returns the value returned by {arg FORM{sub N}}.
}}


For some applications, the restoration operation must be different depending on whether the computation completed successfully or was aborted somehow (e.g., by an error or by typing control-D).  To facilitate this, while the restoration operation is being performed, the value of {var RESETSTATE}{index *PRIMARY* RESETSTATE Var} will be bound 
to {lisp NIL}, {lisp ERROR}, {lisp RESET}, or {lisp HARDRESET} depending on whether the exit was normal, due to an error, due to a reset (i.e., control-D, or in Interlisp-10, control-C followed by reenter), or due to call to {lisp HARDRESET}{index HARDRESET FN} ({PageRef Fn HARDRESET}).  {fn HARDRESET} is only available in Interlisp-D; when control-D is typed at {lisp RAID}, a {lisp HARDRESET} is performed.   As an example of the use of {var RESETSTATE},

{lispcode
(RESETLST
   (RESETSAVE (INFILE X)
      (LIST '[LAMBDA (FL)
                (COND ( (EQ RESETSTATE 'RESET)
                        (CLOSEF FL)
                        (DELFILE FL]
            X))
   {arg FORMS})}


will cause {lisp X} to be closed and deleted only if a control-D was typed during the execution of {arg FORMS}.


When specifying complicated restoring expressions, it is often necessary to use the old value of the saving expression.  For example, the following expression will set the primary input file (to {lisp FL}) and execute some forms, but reset the primary input file only if an error or control-D occurs.

{lispcode
(RESETLST
   (SETQ TEM (INPUT FL))
   (RESETSAVE NIL
      (LIST '(LAMBDA (X) (AND RESETSTATE (INPUT X)))
            TEM))
   {arg FORMS})}

So that you will not have to explicitely save the old value, the variable {var OLDVALUE}{index OLDVALUE Var} is bound at the time the restoring operation is performed to the value of the saving expression.  Using this, the previous example could be recoded as:

{lispcode
(RESETLST
   (RESETSAVE (INPUT FL)
      '(AND RESETSTATE (INPUT OLDVALUE)))
   {arg FORMS})}


As mentioned earlier, restoring is performed by applying {fn CAR} of the restoring expression to the {fn CDR}, so {lisp RESETSTATE} and {lisp (INPUT OLDVALUE)} will not be evaluated by the {fn APPLY}.  This particular example works because {fn AND} is an nlambda function that explicitly evaluates its arguments, so {fn APPLY}ing {fn AND} to {lisp (RESETSTATE (INPUT OLDVALUE))} is the same as {fn EVAL}ing {lisp (AND RESETSTATE (INPUT OLDVALUE))}.  {fn PROGN} also has this property, so you can use a lambda function as a restoring form by enclosing it within a {fn PROGN}.



The function {fn RESETUNDO}{index RESETUNDO FN} ({PageRef Fn RESETUNDO}) can be used in conjunction with {fn RESETLST} and {fn RESETSAVE} to provide a way of specifying that the system be restored to its prior state by {it undoing} the side effects of the computations performed under the {fn RESETLST}.




{Begin Note}
Date: 20 NOV 1981 1737-PST
From: SHEIL.PA
Subject: RESETFORM no longer traps ↑D

Simple example:

(DEFINEQ (ALPHA NIL (RESETFORM (BETA T) (READ]
(DEFINEQ (BETA (X) (PRIN1 (IF X THEN 'IN ELSE 'OUT))(NOT X]

If the READ is exited normally, or via ↑E, the "OUT" msg gets printed. If the exit is by ↑D, it does not.

Needless to say, this is producing much wierd and wonderous behavior in all parts of the system when ↑D's are done.

Loadup of 18-NOV 20:41.

Date: 20 NOV 1981 1743-PST
From: SHEIL.PA
Subject: RESETFORM bug

Only seems to happen when the process scheduler is running.  Turing it off reproduces the normal behavior.

Date: 20 Nov. 1981 5:57 pm PST (Friday)
From: vanMelle.PA

Right, soft ↑D in the process world doesn't do resetrestores (they won't get done until hard ↑D, I guess).  The reason being that ↑D doesn't really take you to the top.  I could try to fix this; however, independent of this, RESETFORM and friends are dangerous in the process world because of their dependence on a peculiar global resource.  We really need unwind-protect.

Date: 20 NOV 1981 1759-PST
From: SHEIL.PA
Subject: More on ↑D...

its not that simple. Consider:

(DEFINEQ (GAMMA (PROG1 (NLSETQ (READ))(PRIN 'HI]

calls PRIN if the exit from the READ is normal or ↑E, but not ↑D. Whether or not processes are running.

Date: 20 NOV 1981 1802-PST
From: SHEIL.PA

The execution of resetrestores shouldnt be dependent on going to "the top".  In fact, it's precisely the nested forms that have to be undoewn to get, for example, teletype output to go to the right place when one has subsystems like the window break package running.

Date: 20 Nov. 1981 6:08 pm PST (Friday)
From: vanMelle.PA

Indeed, they shouldn't depend on "getting to the top".  Unfortunately, ↑D (even in non-process world) is NOT implemented by doing repeated ↑E's.  Perhaps it should be, since that is what it is conceptually.

Date: 20 Nov. 1981 6:09 pm PST (Friday)
From: vanMelle.PA

(DEFINEQ (GAMMA (PROG1 (NLSETQ (READ))(PRIN 'HI]

wouldn't call PRIN after ↑D in ANY Interlisp.

Date: 22 NOV 1981 1707-PST
From: SHEIL.PA

How embarrasing to  find such a hole in one's model of the system. Indeed, chapter 5 specifies that ↑Ds are not caught by errorsets, even though chapter 16 makes no reference to this. Such a simple model too: ↑D = repeated ↑Es and RESETx implemented in terms of errorset (the ONE error catching mechanism) in the obvious way.

At the risk of being seen to weasel my way out of my embarrasment by changing the spec, what IS wrong with that simple model?

Date: 22 NOV 1981 2334-PST
From: KAPLAN.PA

Well, if we're going to do it "right", we might as well track down the RETFROM's in DWIM that are causing the TEST program to eat up storage, and fix them too.
{End Note}


{Begin Note}
Date: 8 March 1982 11:05 am PST (Monday)
From: Masinter.PA
Subject: Re: How do I do the following
In-reply-to: Bobrow's message of 3 March 1982 4:42 pm PST (Wednesday)

On second reading, I discovered that the problem is the misinterpretation of
RESETVAR:

You tried:

a) in a (PROG((FIRSTCOL 16)) ...)
b) in a (RESETVAR FIRSTCOL 16 ...)

Neither of these successfully re-sets the top level value of FIRSTCOL within the
scope of the "...". You either want:

(RESETLST   (RESETVAR FIRSTCOL 16)   forms)

or

(RESETVARS ((FIRSTCOL 16)) forms )

As the way of resetting the top level value of FIRSTCOL while executing
"forms".

Date: 8 March 1982 12:03 pm PST (Monday)
From: KAPLAN.PA
Subject: Re: How do I do the following

Larry,
  According to the manual, Danny is right about the use of RESETVAR.  Without looking at the code, I'm not sure that Bill's account is correct either, since Danny was explicitly printing to a file, not a window.  My recollection is that COMPUTEPRETTYPARM only finagles output to the terminal.  Is that correct?
{End Note}


{Begin Note}
Date:  9-Sep-82 16:18:21 PDT (Thursday)
From: Masinter.PA
Subject: RESETLST vs RESETFORM

This should be in the manual, or maybe we should fix RESETFORM.

Example: this doesn't work:
(RESETFORM (DEFPRINT 'A 'FOO) stuff)

This is what DOES work:

(RESETLST (RESETSAVE NIL (LIST 'DEFPRINT 'A (DEFPRINT 'A 'FOO)))
	   stuff)

we really need unwind-protect.

Date: 14 Sept. 1982 8:54 am PDT (Tuesday)
From: JonL.pa
Subject: Re: RESETLST vs RESETFORM

Re:
   (RESETFORM (DEFPRINT 'A 'FOO) stuff)
versus
    (RESETLST (RESETSAVE NIL (LIST 'DEFPRINT 'A (DEFPRINT 'A 'FOO)))
	   stuff)

I believe the problem with the former is is that DEFPRINT takes two arguments, whereas RESETFORM is documented so as to imply that it works only for functions of one argument (and which return their "old values").  There is a certain lack of functionality in RESETFORM, but then again, it is advertised only to be a "shorthand" for some RESETLST/RESETSAVE usage.

Incidentally, the unique feature of UNWIND-PROTECT is the running of its "cleanup forms" in the same environment as the lexically-surrounding code; the particular example with DEFPRINT is quite independent of its surrounding lexical environment, and would be a "good" usage of RESETFORM except for the above-mentioned lack of functionality.


Date: 30 SEP 1982 1119-PDT
From: SHEIL.PA
Subject: two comments on the RESETFORM macro

Currently, the RESETFORM macro evaluates the resetform (a) outside of errorset protection and (b) some time (during which an interrupt can occur) before the resetlst entry for undoing it is made. This could be disasterous if one is resetting, for example, a display stream clipping region and the user bombed you out. [(a) may or may not be a bug or non-feature, depending on how one reads the manual; (b) is nasty to fix and probably requires interrupt protection]

Also, the RESETFORM macro doesnt do a very good job if one passes it a LAMBDA expression as the reset function (two copies wind up in the compiled code).  [The motivation for this is for two arg fns like DSPCLIPPINREGION, as
	(RESETFORM ((LAMBDA (X) (DSPCLIPPINGREGION X window)) NEWREG)
		forms)
is much more elegant than
	(RESETLST (RESETSAVE NIL (LIST 'DSPCLIPPINGREGION
					(DSCLIPPINGREGION NEWREG window)
					window))
		forms)
In fact, since I just realized that, it might be worth noting this trick in the manual for other slow thinkers.]

*** I really want to shift to this notation in DEDIT, so this patch would be greatly appreciated ***

Beau

PS: From a slightly broader point of view, it would be nice to have a wizard scrutinize these macros as (a) this is not the first non-feature report for them (b) they dont look to be as good code as they could be. Last time I raised this, the discussion quickly expanded to include respecifying the whole error handling machinery (and thus nothing happened). Perhaps a useful intermediate step would be to define a few more useful abstractions, such as CATCH and THROW, which we could start using in our code to replace the convoluted RESETLST constructions that tend to generate these discussions in the first place.
{End Note}


}{end subsec Changing and Restoring System State}