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