[comp.sys.handhelds] CLEARing stack

sburke@jarthur.Claremont.EDU (Scott Burke) (01/29/91)

Here are some more thoughts on stack clearing.

As a commerical software developer for the HP-48SX, I can attest that of all
the issues involved in a major piece of software, by far the trickiest is the
error handling.  Tortuous programming algorithms pale in comparison to being
"polite" and not changing the state of the calculator, _whatever_ the user
happens to do.

In general, storing the stack in a list and a global or local variable is
bad for several reasons:

  1. There may be a large object on the stack, and if you want your code to
     run in low memory conditions, it is a _really_ bad idea to stick those
     1K GROBs (to pick an example) in a list and try to store it.

  2. Using a global variable during programming should only be done when
     parameters must be stored _between_ executions of the code--just like
     HP does with 'PTpar' to store Periodic Table parameters.  Otherwise,
     using global variables is never necessary, unless using a built-in
     RPL command that requires them (like ROOT), or unless providing stack
     access during a program, where you would like to give the user access to
     their data.  Obviously, globals must be used for data files.  However,
     (here is the long-winded point of this paragraph), creating and deleting
     global variables is _slow_ _slow_ _slow_!!  Much better to use locals.
     One other advantage is that even if the user manages to escape your code
     at an unpredicted place, there aren't a whole bunch of temporary globals
     sitting in their directory.  Name conflicts are also irrelevant if 
     locals are used.

  3. The _real_ reason it's a bad idea to store the stack away is that during
     input of data (say, with the INPUT statement), the user has _no_ access
     to their current stack.  What if the user wants to do a bunch of cal-
     culations on the stack and then insert those calculated values into a
     data field in a program?  This functionality is supported in the EDIT
     menu with the ^STK key, which provides crippled access to the inter-
     active stack of the 48SX.  But if you've dumped all the stack into a
     local variable, the user can't do that.  _Bad_!  Also, if your code 
     supports copying values to the stack, the list-based scheme would have 
     to append new values to the stored list.  Finally, if the user breaks 
     out of the code unexpectedly, their stack is gone.

So how do you get around these problems?  In an earlier message, Eric (edp)
hit on the solution I have found to work acceptably:  to use a stack depth
counter and then upon exiting the program, restore the correct depth of the
stack.  This simply changes the headache, however, because now the code has
to be absolutely sure what the user has done to the stack in the program (by
this, I mean how many values have they _added_ to the stack, since there is
no way to delete objects on the stack without stack access).

Tracking a stack depth is a simple matter in theory--just do an initial
DEPTH and then increment the local variable each time the stack size is
increased with a ->STK press (or whatever).  Whoever complained about not
being able to comprehend how it would be possible to not know the stack
depth merely needs to imagine this scenario:  The code has exploded two 
lists onto the stack and is doing a meta-object filter pass (i.e., simply
looping from 1 to the size of one of the lists), performing some sort of
concatenation operation (or whatever) each time through the list.  If the
user breaks out of the code, who knows how many iterations have been per-
formed, and therefore how much of the original lists remain, or how many
new entries have been created?  I have written code with hundreds of items
on the stack, because stack operations take noticeably less time than 
dealing with lists or variables.  If the user hits the ATTN key in the 
middle of a loop or a sort operation, then it is not possible to know how
much has happened so far.

All this is well and good, but tracking a stack depth pointer or storing 
the stack in a list will both fail if the user is allowed to break out of
the code unpredictably.  Only a few of the routines posted to the net make
very much attempt to trap ATTN presses, but this is crucial for commercial
software (such as HP's card).  In user language, the only way to do this
is to layer the code with IFERR loops, placing the restoration code after
all the error traps.  In internal RPL code, there are ways to turn off the
ATTN vector, but I stick to user language because of the limited document-
ation available.  I toss SYSEVALs in to speed up crucial loops and GROB
operations, but by and large there are too many dangers associated with
some of the internal routines, unless you have access to _all_ of them.
This means that while HP can write code that is truly impervious to user
abuse, we on the outside are doomed to crashed code after sufficiently
many ATTN presses.  The solution is just to trap a certain number (say 5
or 10) and let the user beware if they are determined to be malicious.

Scott.


sburke@jarthur.claremont.edu

frechett@spot.Colorado.EDU (-=Runaway Daemon=-) (01/29/91)

In article <10535@jarthur.Claremont.EDU> sburke@jarthur.Claremont.EDU (Scott Burke) writes:
>Here are some more thoughts on stack clearing.
>hit on the solution I have found to work acceptably:  to use a stack depth
>counter and then upon exiting the program, restore the correct depth of the
>stack.  This simply changes the headache, however, because now the code has
Ummm excuse me if this sounds too simple but why do you need a stack counter 
that keeps track of additions and subtractions from the stack.?

>Scott.
>sburke@jarthur.claremont.edu

Let me work this out.  If start the program with DEPTH -> STKdpth
at ANY point in the program, I could kill everything but that original stuff
with a routine like this.. Here is a sample program that I just tried out.
\<< DEPTH \-> STKdpth 
  \<< HALT
DEPTH STKdpth DROPN 
  \>>
\>>

When executed it stops at HALT.. I can do anything that I damn well please to
the stack short of killing the original stuff that was on the stack.. 
for my test I had.
4:         4
3:         3
2:         2
1:         1
and when it halted, I entered as much crap as I could stand to add to the
stack.  Performed Calculations etc.. 
then hit CONT and poof the only thing left on the stack was.
4:         4
3:         3
2:         2
1:         1

I can't think of ANYTHING simpler than this..
If you have a program that tends to devour stuff that was previously on the 
stack then I pity your program.. 
If it is supposed to eat some arguments then it is a simple matter to subtract
1 or 2 from STKdpth.  

I just am not sure what you meant by having all this keeping track of stack
stuff.  Take the example you gave of EDIT and the ^STK function. Let's say I 
was going to write EDIT with my routines... I might define the ^STK function as
something like 
\<< DEPTH STKdpth ->LIST          @puts any current program stuff in a list.  
    -> Temp.shit                  @Put list in local Var.  Who cares if it dies.
    LOOKATSTACK_ROUTINE           @the catalog like thing that ^STK does.
Temp.shit LIST-> ROLL             @Bet we will deal with ECHOed stuff next.
\>>

Give me an example where this wouldn't screw up other than HUNGRY programs
or of course ones that use CLEAR.. ;)

	ian
--

-=Runaway Daemon=-

frechett@spot.Colorado.EDU (-=Runaway Daemon=-) (01/29/91)

DAMN.  Let me fix something in my previous post
substitute every case of 
DEPTH STKdpth <anything>
to 
DEPTH STKdpth - <anything>

That is how it looks in my calculator and that is how it WORKS 
but I didn't show that in my post.  Sorry if this causes undo 
confusion.. ;)

	ian
--

-=Runaway Daemon=-

sburke@jarthur.Claremont.EDU (Scott Burke) (01/29/91)

Response to ian:

So far, in all the code I have written, there have been no unavoidable
places where it was necessary to provide stack access to the user.  Therefore
the initial stack remains pure and should be preserved.  I have never found
a need for the HALT statement, and regard its use as non-intuitive and to be
avoided.  There is a case, I admit, where it _makes sense_ to provide stack
access in one of my programs, but in that case, I create a single parameter
file and quit the program; if the user re-executes the library, and if a 
parameter file of the correct format exists, then the user is provided an
additional menu choice to return to where they were before.

You also discuss additions and subtractions to the stack.  What I mean by
that is (to use another HP example) the case where the user runs the Periodic
Table and presses the ATWT key, thereby leaving the Atomic Weight (a tagged
object) on the stack.  The stack depth pointer must be updated, because 
otherwise, when the restoration routine (basically DEPTH counter - DROPN)
runs, it will obliterate the desired atomic weight.  Does this make sense?
As for deleting stack entries, unless the user is given access with HALT, 
they cannot delete entries, because the stack access in the Interactive Stack
in the INPUT/EDIT menu is crippled and the delete key is not functional.
Obviously my code could eat entries on the stack, but if it did so, i also
would pity it.  That is not a worry.

Again, the reason for having a current depth pointer is to know how many
items to drop at termination.  It is not possible in complex code to always
know how many temporary items are on the stack at any given time unless
absolutely every calculation takes place through local variables, making
use of STO+, etc., and not leaving more than 1 or 2 items on the stack at
any time.  That is one solution, but it is by far and away the slowest one.

I guess the key point I was trying to make is that you have to enable the 
user to copy to the stack an arbitrary (and therefore unknowable in advance)
number of values or whatever, by means of a ->STK operation.  To track this,
it is either necessary to append those values to the original stack stored
in a list (which is not desirable for reasons i mentioned in the previous 
post) or to just place the desired values on the stack and increment the
depth pointer.  It is therefore crucial to not allow the user to break out of
any code before it updates the depth pointer and propagates that change in
depth upwards to any parent routines.  Thus the IFERRs.

I think I have answered your criticisms.

Scott.

sburke@jarthur.claremont.edu

edp@jareth.enet.dec.com (Eric Postpischil (Always mount a scratch monkey.)) (01/30/91)

In article <10535@jarthur.Claremont.EDU>, sburke@jarthur.Claremont.EDU (Scott
Burke) writes:

>  1. There may be a large object on the stack, and if you want your code to
>     run in low memory conditions, it is a _really_ bad idea to stick those
>     1K GROBs (to pick an example) in a list and try to store it.

Why is that?  Supposing that you disable argument/stack saving temporarily and
convert the stack to a list to be stored in a variable, are extra copies of the
items on the stack created during this?  Or does the 48 just move them around?


				-- edp (Eric Postpischil)
				"Always mount a scratch monkey."
				edp@jareth.enet.dec.com

sburke@jarthur.Claremont.EDU (Scott Burke) (01/30/91)

In article <19558@shlump.nac.dec.com> edp@jareth.enet.dec.com (Eric Postpischil (Always mount a scratch monkey.)) writes:
>In article <10535@jarthur.Claremont.EDU>, sburke@jarthur.Claremont.EDU (Scott
>Burke) writes:
>
>>  1. There may be a large object on the stack, and if you want your code to
>>     run in low memory conditions, it is a _really_ bad idea to stick those
>>     1K GROBs (to pick an example) in a list and try to store it.
>
>Why is that?  Supposing that you disable argument/stack saving temporarily and
>convert the stack to a list to be stored in a variable, are extra copies of the
>items on the stack created during this?  Or does the 48 just move them around?
>
>
>				-- edp (Eric Postpischil)
>				"Always mount a scratch monkey."
>				edp@jareth.enet.dec.com

You've got a correct point here, sort of...  ;-)  It _is_ possible to disable
Last Arguments from a program by setting flag -55.  However, it is _not_
possible to disable Last Stack (i.e., flush the Last Stack buffer).  Last
Stack most likely uses an approach like storing the previous stack in a list
somewhere, and then restoring that list when Last Stack is pressed.  However,
to clear the Last Stack buffer requires either an ON-C (not terribly feasible
from within a program) or a 0 DROP (to place a 0 in the buffer) _from the
keyboard_!  If the program does a 0 DROP it won't affect the user stack.
Therefore, whatever is in Last Stack is not changeable by a program without
a HALT statement.

You are correct (I've been doing some fiddling in lo-mem situations) when
you say that the 48 just moves things around, _except_ for this case:  If
the user has merely recalled a GROB (to pick an example) to the stack, then
all that is on the stack is a pointer to the stored GROB.  If your program
proceeds to create a list of the stack, it is forced to do a NEWOB to make
a copy of that GROB to go into the list, thereby eating memory.  On the other
hand, if the GROB really is a new item on the stack to begin with, you don't
lose anything by hiding it away in a list, because the GROB was already 
occuping space in Temporary Object Memory.  But you just don't know if the
stack is pointers or real objects, so it can't hurt to leave it alone.

I agree that for speed and space, CLEAR is nice.  Although you still have to
error trap (to have a chance to CLEAR after an ATTN press), you don't have to
write the code to muck with the stack depth counter.  And if your code does
not have to access the stack (or provide Interactive access to it), binding
it in a list may not be a terrible solution, and will let you use CLEAR.

Let me throw out another idea for discussion:  How many ATTN presses should a
commercial piece of software trap?  Impatient (or ignorant) users may be 
inclined to press that dumb key a whole bunch if they don't perceive that 
anything is happening (or if they don't know about the Hourglass symbol).
For example, Donnelly's QSORT ignores ATTN presses and goes right on ahead
to finish the sort.  This means that a user sorting a lot of data may very
well push ATTN four or five times and then give up and wait.  As soon as the
QSORT call is completed, WHAMMO your code crashes because you didn't trap 
enough ATTN presses, and you leave 85 data items and a bunch of garbage on
the stack!  At what point should you throw up your hands and say, "I can't 
use internal code, so I'm doomed!"?

Scott.

sburke@jarthur.claremont.edu

akcs.falco@hpcvbbs.UUCP (Andrey Dolgachev) (02/02/91)

O.K. I get the point, time to change the ol' Tetris program again.
Another idea, which somebody mentioned is to put a unique object on the
stack and then at the end of the program, simply run this loop
DO UNTIL (whatever the unique object is) == END
Of course, the problem with this idea, is that although it is flexible,
it does not allow the user to use objects which were left on the stack as
arguments for INPUT or such.  However, this is also a problem with the
other ideas, like DEPTH stackdepth - DROPN, posted by Ian.
There must be a perfect solution which HP uses, right?
    ---Falco