HP Forums
System RPL - How to exit indefinite loop? - Printable Version

+- HP Forums (https://www.hpmuseum.org/forum)
+-- Forum: HP Calculators (and very old HP Computers) (/forum-3.html)
+--- Forum: General Forum (/forum-4.html)
+--- Thread: System RPL - How to exit indefinite loop? (/thread-17254.html)



System RPL - How to exit indefinite loop? - Strike1 - 07-16-2021 02:35 AM

What is the best way to drop out of this indefinite loop, BEGIN … AGAIN? I think I would need to generate an error. But how? The complete code is from Joe Horn’s Full Reset Stack. A double tap of the ON key is supposed to drop out of the program but couldn’t see anything that actually does that. I figured I would need to do that on my own. My guess is to generate an error, do a STOF, and drop out. I’m sure I’m making this way too simplistic. But I’m looking for a place to start.

::
xRCLF
InitSysUI
::
INCOMPDROP
PTR 2F3AA
BEGIN



BEGIN



DUP#0=
UNTIL


ClrDAsOK
GetKeyOb
ERRSET
DoKeyOb
ERRTRAP
SysErrorTrap
AGAIN
;
ERRTRAP
VLM
;
@


RE: System RPL - How to exit indefinite loop? - Joe Horn - 07-16-2021 03:49 AM

(07-16-2021 02:35 AM)Strike1 Wrote:  The complete code is from Joe Horn’s Full Reset Stack.

No idea what that is. Please give us either the original source code, or a link to it.


RE: System RPL - How to exit indefinite loop? - Strike1 - 07-16-2021 12:38 PM

Link: https://groups.google.com/g/comp.sys.hp48/c/ubiYam5PQL4

Code:


!NO CODE
!RPL
::
xRCLF
InitSysUI
::
INCOMPDROP
STOALLFcont2_
BEGIN
AtUserStack
SysMenuCheck
PTR 2A32D
::
EditLExists?
IT
CODE 0001B 1321B82100317314C1308D94150
DispCommandLine
;
?DispStatus
::
PTR 2A28C
::
TOADISP
BINT1
::
StackLineHeight
EditLExists?
?SKIP
#8+
CommandLineHeight
CODE 00028 1331F0CE08147131808B2C034701331458D1B130
#=case
3DROP
DUP4UNROLL
#-
SCANFONT
BEGIN
DUP
NULL$
4PICK
#6+
DEPTH
#>ITE
'NOP
::
4PICK
#5+
PICK
;
BINT74
SysITE
BINT1
BINT0
BINT52
TestSysFlag
ARRYREAL
TestSysFlag
IT
::
7PICK
#1<>
OR
;
BINT95
TestSysFlag
OBJ>R_
3PICK
'NOP
EQ
?SKIP
::
2LIST
TestSysFlag
?SEMI
{
BINT2
BINT3
BINT4
BINT29
BINT5
BINT6
}
4PICK
XEQTYPE
COERCE
SWAPDROP
#=POSCOMP
#0<>
?SEMI
BINT95
SetSysFlag
;
FPTR 2 95
BINT95
R>OBJ_
ITE
SetSysFlag
ClrSysFlag
UNROTDUP
6ROLL
SWAP
5ROLL
EditLExists?
CODE 000CB
8428F86A435508528F86A434606A708FD5F30068FB97601B2B22810007D5CA340400082480B83460​310586280818FA7E21FD8608143C6CAF2CA131D9C6F2C9C98FC0760D4118CA8DC75308FD5F301000​68FB97601BDF818142342B228C4C2F0C21341106D6F
4UNROLL
#-
SWAP#1+SWAP
DUP#0=
UNTIL
3DROP
;
SetDA2aValid
;
;
EditLExists?
?SKIP
CODE 0001B 1321B8210031F314C1308D94150
PTR 2A346
::
BINT0
BINT1
GETDF
DoLabel
BINT22
BINT2
GETDF
DoLabel
BINT44
BINT3
GETDF
DoLabel
BINT66
BINT4
GETDF
DoLabel
BINT88
BINT5
GETDF
DoLabel
BINT110
BINT6
GETDF
DoLabel

CODE 00014 34A80E2136142134808C


SetDA3Valid
;
ClrDAsOK
GetKeyOb
ERRSET
DoKeyOb
ERRTRAP
SysErrorTrap
AGAIN
;
ERRTRAP
VLM
;
@

BYTES: #24C2h 562

- GaaK -


RE: System RPL - How to exit indefinite loop? - Joe Horn - 07-16-2021 05:36 PM

(07-16-2021 02:35 AM)Strike1 Wrote:  ... The complete code is from Joe Horn’s Full Reset Stack. ...

(07-16-2021 12:38 PM)Strike1 Wrote:  Link: https://groups.google.com/g/comp.sys.hp48/c/ubiYam5PQL4

Aha, no wonder I didn't recognize that code: it's not my program. It was written by "GaaK" (Gustavo Portales). I hope somebody here can help you with it.

-Joe-


RE: System RPL - How to exit indefinite loop? - DavidM - 07-25-2021 01:57 PM

As you are probably already aware, a System RPL BEGIN...AGAIN loop is inherently an infinite loop. It has no default mechanism for early exiting or aborting. It can be done, of course, it simply requires the programmer to directly manipulate both the runstream and the return stack as needed for the given situation.

This is definitely an advanced topic for System RPL programming, and I'm not going to attempt to explain all the underlying principles here. Suffice it to say that it can be a powerful tool, but great care needs to be taken when designing the program flow for these structures.

I'll share one way of breaking out of a BEGIN...AGAIN loop that I typically deploy. Note that for this to work, there's a requirement that the AGAIN statement be immediately followed by a ";" (SEMI), which implies that the return stack already has an entry for the codeword that immediately follows the SEMI.

An indefinite loop matching the above description can be exited through the effective use of case/COLA and runstream commands. I usually deploy something like the following:
Code:
<some computation resulting in a boolean value>
case :: RDROP ; ( assuming that TRUE means "exit" )

Notes:
- The somewhat odd-looking encapsulation of a single command inside a secondary may seem strange at first glance, but the secondary is definitely needed in this situation. It's also common to include other processing steps in that secondary, since an early exit at an arbitrary point may require additional clean-up to take place. That makes the secondary a more natural fit.
- This construct must be at the root level of the loop as written. Encapsulating it into another secondary adds an additional return stack level and would require different steps to ensure that the proper level of code is aborted (eg. 2RDROP).
- It's possible to save a tiny amount of space by using a different structure, but IMHO this version is actually more flexible if changes are needed and provides more clarity.

I'll include some simple examples to show how this technique can be applied. Let's say I want to create a small app to show the internal keycodes used by the O/S. I want to loop this so that I can press an arbitrary number of keys to get their codes, and I want to do leave a string ("All done!") on the stack at completion.

Example 1: The Lazy Approach

This code sample is written as an infinite loop, and will just run indefinitely until you interrupt it with ON-C or a hard reset. This might be perfectly fine for a quick-and-dirty test, but I would never use this for something that would be distributed. Forcing the user to do a soft- or hard-reset isn't exactly a friendly way to design an app:
Code:
::
   CK0NOLASTWD                      ( no arguments expected )
   CLEARLCD                         ( clear the screen )
   "Press any key" DISPROW1         ( prompt for pressing keys )

   BEGIN
      WaitForKey DROP               ( don't care about the keyplane )
      #>$ "Key: " SWAP&$ DISPROW3   ( show the key value on line 3 )
   AGAIN

   "All done!"                      ( leave a string on the stack upon exit )
;

Note that as written above, the "All done!" string never sees the light of day. The BEGIN...AGAIN loop is never exited gracefully, so that follow-up code never gets executed.

Example 2: I Hate It When I Do That

OK, so I realize now that I need some trigger built into the look to force an exit. Let's use a somewhat intuitive mechanism: pressing the ON/CANCEL key (O/S key number 47). Armed with the awareness of my special "early exit" construct, I place a test between the key acquisition and value printing statements:
Code:
::
   CK0NOLASTWD                      ( no arguments expected )
   CLEARLCD                         ( clear the screen )
   "Press any key" DISPROW1         ( prompt for pressing keys )

   BEGIN
      WaitForKey DROP               ( don't care about the keyplane )

      BINT47 #=casedrop :: RDROP ;  ( exit if keycode is 47 )

      #>$ "Key: " SWAP&$ DISPROW3   ( show the key value on line 3 )
   AGAIN

   "All done!"                      ( leave a string on the stack upon exit )
;

So I run the app, and it behaves as expected to show key codes until I press ON/CANCEL. But instead of leaving my "All done!" string on the stack, it simply exits with no further action taken. At this point I'm usually slapping my forehead when I realize that, once again, I forgot a fundamental element with this version. In particular, the encapsulating secondary that ends with the AGAIN statement. Not having that, the forced exit jumped to the next return stack entry as it was designed to do, which in this case was the end of the whole program (not the string to be left on the stack).

Example 3: Final Working Version

Armed with all of the above, this version does what I originally intended:
Code:
::
   CK0NOLASTWD                         ( no arguments expected )
   CLEARLCD                            ( clear the screen )
   "Press any key" DISPROW1            ( prompt for pressing keys )

   ::
      BEGIN
         WaitForKey DROP               ( don't care about the keyplane )

         BINT47 #=casedrop :: RDROP ;  ( exit if keycode is 47 )

         #>$ "Key: " SWAP&$ DISPROW3   ( show the key value on line 3 )
      AGAIN
    ;

   "All done!"                         ( leave a string on the stack upon exit )
;

While I have given an example here of how to exit a BEGIN...AGAIN loop, I (intentionally) haven't tailored it to your specific situation. Creating or modifying a System Outer Loop app is a risky endeavor. I encourage you to play around with Gaak's program, but be aware that there are many, many things that can come up with this kind of program that makes them very fragile and prone to unexpected behaviors. Definitely don't load this kind of program on a real calc until you've done extensive testing.


RE: System RPL - How to exit indefinite loop? - Strike1 - 07-25-2021 04:11 PM

Wow. Really helpful! Thank you DavidM.

As I near retirement I decided to revisit programming my favorite calculator.

Before your reply I had decided to change the BEGIN - AGAIN loop to a BEGIN - WHILE - REPEAT loop. Surprisingly it worked. It let me insert the test clause (I employed a User Flag) and it cleanly let me drop out of the loop. Now that you've given me something to study I will try to figure out the merits of a different approach to dropping out of either type loop.

GAAK wrote two versions of this program. One with the menu and one without. They're both similar in structure and the one without the menu has more code. Once again employing a User Flag I had various code skipped or included depending on whether the flag was set or clear. And that works too. I really like that sense of accomplishment when it works. Smile I assigned a toggle-the-flag program to the shift-right down arrow and can bring up the menu or clear it at will.

And I see what you mean by "fragile". If not for Debug4x I might have wasted a good calculator several times over.


RE: System RPL - How to exit indefinite loop? - DavidM - 07-25-2021 07:45 PM

(07-25-2021 04:11 PM)Strike1 Wrote:  I had decided to change the BEGIN - AGAIN loop to a BEGIN - WHILE - REPEAT loop. Surprisingly it worked. It let me insert the test clause (I employed a User Flag) and it cleanly let me drop out of the loop. Now that you've given me something to study I will try to figure out the merits of a different approach to dropping out of either type loop.

When there's a single decision point, BEGIN...WHILE...REPEAT is usually the better choice for clarity, but you can sometimes save some bytes by using alternate methods.

There's also those times when you may already be doing something with the return stack (such as processing a list), and a hybrid approach may be needed.

Since you mentioned using Debug4x, be aware that the compiler may complain about unmatched codewords if you start mixing up the looping commands. It also automatically includes the :: ; tokens in a BEGIN...WHILE...REPEAT sequence, so you shouldn't put those at the outermost level between WHILE and REPEAT in most cases. You may also have to put the codewords in [] to keep the compiler from getting confused by non-standard constructs.