HP Articles Forum
[Return to the Index ]
[ Previous | Next ]
UserRPL stack commands
Posted by James M. Prange (Michigan) on 8 July 2007, 3:51 a.m.
It has been asserted that the RPL stack manipulation commands are "numerous and unintuitive".
Numerous? Well, perhaps, but most of the UserRPL stack commands can be grouped into just three "families", plus three more commands that don't really fit into those families. Unintuitive? I think not, at least for most of them.
With the fixed-depth 4-register stack of "Classic RPN", the stack commands are few and simple. With the added flexibility of RPL's variable-depth stack, having more (and more flexible) stack commands seems desirable. If you really wanted to, you could get by with just the stack commands DEPTH, either ROLL or ROLLD, PICK, and DROPN (along with some extra keystrokes or programming).
With Classic RPN models, the T register is replicated downward as arguments are taken from the bottom of the stack, so an endless supply of whatever is in the T register is available as an argument for additional operations. With RPL's variable-depth stack, this feature isn't available. Perhaps the simplest work-around for the loss of this feature is to store an object in a named variable (local or global), and then calling the variable by name can provide an endless supply of the object to level 1, for as long as the variable exists unchanged. If you need the object to be on a different level, then call it by name and use stack manipulation to move it to wherever you need it. Alternatively, DEPTH PICK will copy the topmost level down to level 1, which has the potential to provide an endless supply of the object in the topmost level to level 1 (as long as the topmost level isn't changed). Of course, if you need the object to be on a different level, then follow DEPTH PICK with a stack manipulation to move it to wherever you need it, or perhaps store it in a variable for later use.
Regarding the RPL model "series", I treat the 28C and 28S as the "28 series", the 48SX, 48S, 48GX, 48G, and 48G+ as the "48 series", and the 49G, 49g+, 48gII, and 50g as the "49 series".
Regarding character translations in this article, "\<<" and "\>>" represent the UserRPL program delimiter characters, and "\->" represents the right-arrow character.
For the "stack diagrams", of course there can be an indefinite number of levels above those that I show, in which case, except for being moved up or down, those higher levels are unaffected.
Regarding LAST, or alternatively LASTARG on the 48 and 49 series, the assumption is that this is enabled, that is, on the 28 series, flag 31 is set, and on the 48 and 49 series, flag -55 is clear.
Where I show sequences or programs that have the same effect as a command, that refers only to the stack itself, not to what LAST or LASTARG will return.
I really don't think that the UserRPL stack commands are difficult to learn, although keeping track of exactly which objects are on which levels can be difficult when you use more than a few levels. Well, you can always use named global or local variables when it seems too difficult to keep track of the stack.
Of course, being careful of the order in which objects are put on the stack in the first place helps to reduce the need for stack manipulation; it may not be needed at all.
If you're going to need an object again, then use a stack manipulation that copies it, and for its final use, use a stack manipulation that moves it instead.
In general, it helps to have a "clean" stack when you start something new. Unneeded objects on the stack use up memory and may slow down execution, and of course add "clutter". Discard (or store in a global variable for future use) anything that you don't need on the stack anymore, either just before starting something new, or else when you finish what you're doing.
Of course, to manipulate a stack level, that level has to actually exist, or else you'll get an error. But some of these commands take a number for an argument, and if the argument is 0, then these (except for UNPICK and NDUPN, each of which requires two arguments) don't error out even with an empty stack.
For stack commands that take a numeric argument, non-integer values are rounded to integer values, and a negative number is treated the same as 0. With the 49 series, either a zint (an "exact integer") or a "real number" can be used. The commands that return a count to the stack always return it as a real number.
Also note that the commands that take a numeric argument first remove the numeric argument from the stack, and then act on the remainder of the stack, so when you use n as the numeric argument, it refers to the stack levels as they will be after n is removed from the stack.
Note that the stack doesn't really have any objects in it. That you seem to see objects in the stack is due to a bit of indirection. The stack is really a stack of 5-nibble pointers (that you don't see) with the objects pointed to elsewhere in memory. When the "stack display" needs to be updated, the objects pointed to are decompiled to string forms suitable for display with the current stack display modes and font size, and those are what you see. The stack manipulation commands can be fast because objects of various types and sizes aren't being manipulated; only the fixed-size stack pointers are being manipulated. For example, when you use DUP to make a "copy of the object on level 1", you aren't really making a copy of the object, you're just making a copy of the pointer, and when you use SWAP to "exchange the objects on levels 1 and 2", you aren't really exchanging the objects, you're just exchanging the pointers.
I've listed the stack commands in the following order:
DEPTH ROLL ROLLD ROT UNROT (added with the 49 series) SWAP PICK PICK3 (added with the 49 series) OVER DUP DUPDUP (added with the 49 series) DUPN DUP2 NDUPN (added with the 49 series) DROPN DROP2 DROP ; (added with the 49 series) CLEAR NIP (added with the 49 series) UNPICK (added with the 49 series)Two more that I wouldn't really consider to be "stack commands", but which are often used with the stack.
\->LIST LIST\->The 48 and 49 series also provide various operations in an "interactive stack". When the "standard stack display" is active (no "special environment" active) this can be invoked by pressing the CursorUp key, and in the 49 series, even when in a special environment, by pressing the HIST key. Some stack operations, such as ECHO, may be available with a menu key in various special environments. I don't cover these operations in this article, but I do recommend that you experiment with them.
DEPTH -> n
DEPTH doesn't manipulate the stack; it simply tells you how many levels existed before you executed DEPTH, and can be useful with some of the stack manipulation commands. Perhaps it's worth noting that executing the DEPTH command increases the stack depth by 1, but usually that new level-1 number will be used as an argument for the next command. For example, to copy the topmost stack level to level 1 (lifting the previous stack by one level), use DEPTH PICK. To move the topmost stack level down to level 1 while lifting the rest of the stack, use DEPTH ROLL. To move level 1 to the top of the stack while moving the rest of the stack down, use DEPTH ROLLD. DEPTH can also be used for testing whether the stack has anything on it; if the stack is empty, then DEPTH returns the real number 0, which the UserRPL conditional commands treat as "false"; otherwise it returns a non-zero real number, which the UserRPL conditional commands treat as "true".
DEPTH has no effect on what LAST or LASTARG returns.
objn ... obj1 n ROLL -> objn-1 ... obj1 objn
obj2 obj1 2 ROLL -> obj1 obj2
obj 1 ROLL -> obj
0 ROLL ->
ROLL first removes its numeric argument n from level 1 (so of course the rest of the stack moves down one level), then moves level n to level 1, while moving all levels below n upward by one level. 1 ROLL ends up leaving the stack the same as it was. 0 ROLL also ends up leaving the stack the same as it was, and doesn't error out even with an empty stack.
If you start with the stack like this:
4: "D" 3: "C" 2: "B" 1: "A"After 3 ROLL, the stack looks like this:
4: "D" 3: "B" 2: "A" 1: "C"n ROLL has the same effect as doing n ROLLD n-1 times.
The effect of n ROLL could be achieved by:
n \<< \-> n \<< 1. n 1. - START n ROLLD NEXT \>> \>>After n ROLL, LAST or LASTARG returns n.
objn ... obj2 obj1 n ROLLD -> obj1 objn ... obj2
obj2 obj1 2 ROLLD -> obj1 obj2
obj 1 ROLLD -> obj
0 ROLLD ->
ROLLD first removes its numeric argument n from level 1, then moves level 1 to level n, while moving all levels below n downward by one level. 1 ROLLD ends up leaving the stack the same as it was. 0 ROLLD also ends up leaving the stack the same as it was, and doesn't error out even with an empty stack.
If you start with the stack like this:
4: "D" 3: "C" 2: "B" 1: "A"After 3 ROLLD, the stack looks like this:
4: "D" 3: "A" 2: "C" 1: "B"n ROLLD has the same effect as doing n ROLL n-1 times.
The effect of n ROLLD could be achieved by:
n \<< \-> n \<< 1. n 1. - START n ROLL NEXT \>> \>>After n ROLLD, LAST or LASTARG returns n.
obj3 obj2 obj1 ROT -> obj2 obj1 obj3
ROT moves level 3 to level 1, lifting the original levels 1 and 2 one level.
ROT does the same as 3 ROLL or 3 ROLLD 3 ROLLD, or on the 49 series, UNROT UNROT.
After ROT, LAST or LASTARG returns whatever was originally on levels 3, 2, and 1.
obj3 obj2 obj1 UNROT -> obj1 obj3 obj2
UNROT moves level 1 to level 3, moving the original levels 2 and 3 down 1 level.
UNROT does the same as 3 ROLLD or ROT ROT or 3 ROLL 3 ROLL.
After UNROT, LAST or LASTARG returns whatever was originally on levels 3, 2, and 1.
obj2 obj1 SWAP -> obj1 obj2
SWAP exchanges levels 1 and 2.
SWAP does the same as either 2 ROLL or 2 ROLLD.
After SWAP, LAST or LASTARG returns whatever was originally on levels 2 and 1.
objn ... obj1 n PICK -> objn ... obj1 objn
obj2 obj1 2 PICK -> obj2 obj1 obj2
obj 1 PICK -> obj obj
0 PICK ->
PICK first removes its numeric argument n from level 1 (moving the rest of the stack down) and then copies level n to level 1, lifting anything that was already on the stack by one level. 0 PICK ends up leaving the stack the same as it was, and doesn't error out even with an empty stack.
If you start with the stack like this:
4: "D" 3: "C" 2: "B" 1: "A"After 3 PICK, the stack looks like this:
5: "D" 4: "C" 3: "B" 2: "A" 1: "C"After n PICK, LAST or LASTARG returns n.
obj3 obj2 obj1 PICK3 -> obj3 obj2 obj1 obj3
PICK3 copies level 3 to level 1, lifting the previous stack by 1 level.
PICK3 has the same effect as 3 PICK.
After PICK3, LAST or LASTARG returns whatever was originally on levels 3, 2, and 1.
obj2 obj1 OVER -> obj2 obj1 obj2
OVER copies level 2 to level 1, lifting the previous stack by 1 level.
OVER has the same effect as 2 PICK.
After OVER, LAST or LASTARG returns whatever was originally on levels 2 and 1.
obj DUP -> obj obj
DUP duplicates level 1, lifting the previous stack by 1 level.
DUP has the same effect as 1 PICK or 1 DUPN.
After DUP, LAST or LASTARG returns whatever was originally on level 1.
obj DUPDUP -> obj obj obj
DUPDUP has the same effect as DUP DUP or 1 PICK 1 PICK; in other words, it makes 2 copies of level 1, lifting the previous stack by 2 levels.
After DUPDUP, LAST or LASTARG returns whatever was originally on level 1.
objn ... obj1 n DUPN -> objn ... obj1 objn ... obj1
obj2 obj1 2 DUPN -> obj2 obj1 obj2 obj1
obj 1 DUPN -> obj obj
0 DUPN ->
DUPN first removes its numeric argument n from level 1, and then it duplicates the lowest n levels, lifting anything above them by n levels. 0 DUPN ends up leaving the stack the same as it was, and doesn't error out even with an empty stack.
If you start with the stack like this:
4: "D" 3: "C" 2: "B" 1: "A"After 2 DUPN, the stack looks like this:
6: "D" 5: "C" 4: "B" 3: "A" 2: "B" 1: "A"n DUPN has the same effect as doing n PICK n times.
The same result could be achieved with:
n \<< \-> n \<< 1. n START n PICK NEXT \>>After n DUPN, LAST or LASTARG returns n.
obj2 obj1 DUP2 -> obj2 obj1 obj2 obj1
DUP2 duplicates levels 1 and 2, lifting the previous stack by 2 levels.
DUP2 has the same effect as 2 DUPN, or OVER OVER, or 2 PICK 2 PICK.
After DUP2, LAST or LASTARG returns whatever was originally on levels 2 and 1.
obj n NDUPN -> obj ... obj n
obj 2 NDUPN -> obj obj 2.
obj 1 NDUPN -> obj 1.
obj 0 NDUPN -> 0.
NDUPN first removes both arguments from the stack, then pushes obj to the bottom of the stack n times, and then pushes the count n as a real number to level 1, lifting the rest of the stack n+1 levels. The result is that you end up with a total of n copies of obj in levels 2 through n+1, and n as a real number in level 1. obj 0 NDUPN just returns a count of 0., without pushing obj to the stack at all.
If you start with the stack like this:
4: "D" 3: "C" 2: "B" 1: "A"After "New" 3 NDUPN, the stack looks like this:
8: "D" 7: "C" 6: "B" 5: "A" 4: "New" 3: "New" 2: "New" 1: 3.The same effect could be achieved with:
obj n \<< @ Begin program. 0. @ Initial value for 'c' (a loop counter). \-> o n c @ Bind local variables. \<< @ Begin local variables defining procedure. 0. @ Push 0. (initial loop count) to the stack. WHILE @ Begin loop test. n < @ Compare loop count with n. REPEAT @ If true, begin REPEAT clause. o @ Push a copy of object to the stack. 'c' INCR @ Increment loop count, leaving a copy of the new value on the stack. END @ End of loop. c @ Push final value of loop count to the stack. \>> @ Abandon local variables. \>> @ End program.After obj n NDUPN, LAST or LASTARG returns both obj and n.
objn ... obj1 n DROPN ->
obj2 obj1 2 DROPN ->
obj 1 DROPN ->
0 DROPN ->
DROPN first removes its numeric argument n from level 1, then discards levels 1 through n. Anything above level n moves down n levels. 0 DROPN leaves the stack the same as it was and doesn't error out even with an empty stack.
If you start with the stack like this:
4: "D" 3: "C" 2: "B" 1: "A"After 3 DROPN, the stack looks like this:
1: "D"After n DROPN, LAST or LASTARG returns n.
obj2 obj1 DROP2 ->
DROP2 discards levels 1 and 2. Anything above that was above level 2 moves down 2 levels.
DROP2 does the same as 2 DROPN.
After DROP2, LAST or LASTARG returns whatever was originally on levels 2 and 1.
obj DROP ->
DROP discards level 1. Anything that was above level 1 moves down 1 level.
DROP does the same as 1 DROPN.
After DROP, LAST or LASTARG returns whatever was originally on level 1.
(A command represented in UserRPL by the semicolon character.)
obj ; ->
; ->
; does the same as DROP if there's anything on the stack, or if the stack is empty, then it does nothing. I think that this was intended for use in ALG mode, but it works in RPN mode. The same result could be achieved by:
\<< IF DEPTH THEN DROP END \>>or:
\<< IFERR DROP THEN END \>>
If the stack was originally non-empty, after ;, LAST or LASTARG returns whatever was originally on level 1. If the stack was originally empty, then ; has no effect on what LAST or LASTARG returns.
objn ... obj1 CLEAR ->
CLEAR ->
CLEAR simply discards all stack levels. Doesn't error out with an empty stack.
CLEAR does the same as DEPTH DROPN.
CLEAR has no effect on what LAST or LASTARG returns.
obj2 obj1 NIP -> obj1
NIP leaves level 1 as is and discards level 2. Anything that was above level 2 moves down 1 level.
The effect of NIP can be achieved with SWAP DROP.
If you start with the stack like this:
4: "D" 3: "C" 2: "B" 1: "A"After NIP, the stack looks like this:
3: "D" 2: "C" 1: "A"
After NIP, LAST or LASTARG returns whatever was originally on levels 2 and 1.
objn ... obj1 obj n UNPICK -> obj ... obj1
obj2 obj1 obj 2 UNPICK -> obj obj1
obj1 obj 1 UNPICK -> obj
obj 0 UNPICK ->
UNPICK first takes both arguments obj and n from the stack and then replaces level n with obj, without moving the rest of the stack. obj 0 UNPICK leaves the stack as it was, and doesn't error out even with an empty stack.
For example, if you start with the stack like this:
4: "D" 3: "C" 2: "B" 1: "A"After "New" 3 UNPICK, it will look like this:
4: "D" 3: "New" 2: "B" 1: "A"The same result (but with slower execution) could be achieved by:
obj n \<< DUP @ Copy of n. 2. + ROLL @ Move down the object to be replaced. DROP @ Discard it. ROLLD @ Move the replacement up to level n. \>>After n UNPICK, LAST or LASTARG returns n.
objn ... obj1 n \->LIST -> { objn ... obj1 }
obj2 obj1 2 \->LIST -> { obj2 obj1 }
obj 1 \->LIST -> { obj }
0 \->LIST -> { }
\->LIST takes its numeric argument n from the stack, and then puts objects from level n down through level 1 into a list, moving the rest of the stack down by n-1 levels. 0 \->LIST puts an empty list on level 1, lifting the rest of the stack by 1 level.
For example, if you start with the stack like this:
4: "D" 3: "C" 2: "B" 1: "A"After 3 \->LIST, it will look like this:
2: "D" 1: { "C" "B" "A" }
After n \->LIST, LAST or LASTARG returns n.
{ objn ... obj1 } LIST\-> -> objn ... obj1 n
{ obj2 obj1 } LIST\-> -> obj2 obj1 2.
{ obj } LIST\-> -> obj 1.
( ) LIST\-> -> 0.
LIST\-> puts the contents of a list on the bottom of the stack, and then pushes a count of the objects to level 1 as a real number, lifting the rest of the stack by the number of object in the list.
For example, if you start with the stack like this:
2: "D" 1: { "C" "B" "A" }After LIST\->, it will look like this:
5: "D" 4: "C" 3: "B" 2: "A" 1: 3.After LIST\->, LAST or LASTARG returns whatever was previously on level 1.
Of course, SysRPL has a lot more stack commands available, so sometimes with SysRPL you can achieve your goal with fewer stack manipulations, saving time and memory usage. As with most things when using RPL, a problem may be that there are so many different ways to accomplish the same thing.
This isn't really what I'd call "stack manipulation", but it seems related and can be useful. DEPTH \->LIST puts the contents of the entire stack into a list that can be stored in a variable for safe-keeping, so you can start working on something else with a "clean slate" without losing what you were working on. This can also be useful in a program when you don't have an easy way of knowing how many objects will be put on the stack; of course the DEPTH command can tell you where the top of the stack is, and thus how many objects you've put on the stack. When you want to restore the saved stack (inserted below anything on the current stack), recall the list to the stack and do LIST\-> DROP. With SysRPL, you can do similar things (with the 49 series) with the "virtual stack" commands.
For "interactive use" (not within a program), assuming that "last stack" saves are enabled, an alternative way to save a copy of the current stack is to do a HALT to save it as the "last stack" within a "suspended environment", then CLEAR to get your "clean slate" for working on something else, and then when you want to restore your saved stack, do CONT immediately followed by the UNDO operation (on a 48SX/S, LeftShift STACK, above the 2 key) to replace the current stack with the saved stack. But note that if you press ENTER (or any key that invokes an implicit ENTER) between CONT and UNDO, then the saved stack will be lost.
Regards,
James
Edited: 26 Sept 2007, 12:23 a.m.