Post Reply 
How do I create a Subroutine using System RPL?
09-17-2022, 07:57 PM
Post: #1
How do I create a Subroutine using System RPL?
Suppose I have the following code and I wanted to have a subroutine for when values are equal or when they are different. How do I define a subroutine?

Code:

RPL
::
    CK2
    ::
    CK&DISPATCH1
        #1 :: ( Parameter is of type Real )
          SWAP
          CK&DISPATCH1
          #1 :: ( Parameter is of type Real )
            SWAP
            %=
            ITE
            :: ValuesAreEqual ;
            :: ValuesAreDifferent ;
          ;
        ;
    ;
;

I tried as below and that result in crash:
Code:

NULLNAME ValuesAreEqual
::
   "Equal are the values"
;

Thank you!
Find all posts by this user
Quote this message in a reply
09-17-2022, 08:30 PM
Post: #2
RE: How do I create a Subroutine using System RPL?
(09-17-2022 07:57 PM)wmundstock Wrote:  Suppose I have the following code and I wanted to have a subroutine for when values are equal or when they are different. How do I define a subroutine?

Wrong group. System RPL is for HP-50, not Prime.

Tom L
Cui bono?
Find all posts by this user
Quote this message in a reply
09-18-2022, 01:01 AM
Post: #3
RE: How do I create a Subroutine using System RPL?
I moved the thread so SysRPL folks will see it.

--Bob Prosperi
Find all posts by this user
Quote this message in a reply
09-18-2022, 03:14 AM
Post: #4
RE: How do I create a Subroutine using System RPL?
(09-17-2022 07:57 PM)wmundstock Wrote:  Suppose I have the following code and I wanted to have a subroutine for when values are equal or when they are different. How do I define a subroutine?
...

What is the development tool you're using here?

"NULLNAME <objectname>" would create a reference for a hidden subroutine if the code is included in a project that is targeting a library for output. How you do that differs depending on the development environment you're using.

Also worth noting: the calling program object you've shown may work, but is inefficient in structure. While it is sometimes necessary to have nested dispatch scenarios, this isn't one of the situations where you would go that route. This situation is actually a straightforward application of the normal dispatch model, which accommodates a check for two reals as it appears you need here.

Writing the code for the Debug4x environment would look like this (using the same equality check and result you defined):
Code:
RPL

INCLUDE SandboxLibrary.h

xNAME SBLIB
::
   CK2&Dispatch
   REALREAL ::  ( note: REALREAL is in ROM and defined as #00011 )
      %= ITE
         ValuesAreEqual
         ValuesAreDifferent
   ;
;

NULLNAME ValuesAreEqual
::
   "Equal are the values"
;

NULLNAME ValuesAreDifferent
::
   "Different are the values"
;

The above code does the standard stack check for 2 arguments being present, then dispatches to the only procedure definition if both arguments are reals (or a mixture of reals/ZINTs, since the ZINTs will automatically be converted to reals by the dispatch code).

Technically speaking, there's also no need for the "::" and ";" with the two NULLNAME procedures, since there's only a single object being encapsulated. But it will work just fine with them as-is.

The above compiled and ran successfully with Debug4x.
Find all posts by this user
Quote this message in a reply
09-19-2022, 09:27 PM
Post: #5
RE: How do I create a Subroutine using System RPL?
You don't need a subroutine in this case. You could just put the code inline:
Code:
 %=
 ITE
 :: all the commands for Values are Equal ;
 :: all the commands for Values are different ;
Find all posts by this user
Quote this message in a reply
09-28-2022, 10:10 PM (This post was last modified: 09-29-2022 02:17 PM by wmundstock.)
Post: #6
RE: How do I create a Subroutine using System RPL?
Thank very much David!
So much useful information on such a small message...

(09-18-2022 03:14 AM)DavidM Wrote:  What is the development tool you're using here?
I am using Debug4X.

(09-18-2022 03:14 AM)DavidM Wrote:  "NULLNAME <objectname>" would create a reference for a hidden subroutine if the code is included in a project that is targeting a library for output.
That was definitely it. I was not compiling it as a library. How would one do a subroutine in a regular program that is not installed as a library?
I found the following example, but seems rather overly complicated for something simple.
Code:
RPL 
( C:\Users\User\Documents\HP Projects\SystemRPL\NonLibrarySubroutines\CMP.s, part of the NonLibrarySubroutine.hpp project, created by <> on 29/09/2022 )

* Main program starts here
::

    ' ( puts next object into the stack, in this case some short code )
    ::
       "Equal are the values"
    ;
    
    ' ( puts the second function to the stack )
    ::
       "Different are the values"
    ;
    
    ( Name each one of the LAMS as below. )
    { LAM ValuesAreEqual
            LAM ValuesAreDifferent }
            
    BIND ( This "stores the LAMs - creates temporary context )
    ( Carry on with your MAIN program )
    CK2&Dispatch
 REALREAL ::  ( note: REALREAL is in ROM and defined as #00011 )
    %= ITE
       LAM ValuesAreEqual ( this is how to call a LAM )
       LAM ValuesAreDifferent
 ;
        ABND ( Purge the temporary variables ).
        
;


(09-18-2022 03:14 AM)DavidM Wrote:  Also worth noting: the calling program object you've shown may work, but is inefficient in structure. While it is sometimes necessary to have nested dispatch scenarios, this isn't one of the situations where you would go that route. This situation is actually a straightforward application of the normal dispatch model, which accommodates a check for two reals as it appears you need here.
I was not aware of this form that you presented with the REALREAL, thanks for sharing this! Much easier and more efficient this way!

(09-18-2022 03:14 AM)DavidM Wrote:  Technically speaking, there's also no need for the "::" and ";" with the two NULLNAME procedures, since there's only a single object being encapsulated. But it will work just fine with them as-is.
Awesome! I was not aware of that either. Thanks for the note.

I have a final question here, how would one learn about the REALREAL thing? I found references of it in the document Programming in System RPL Second Edition, but nothing self explanatory. Is there another documentation that you recommend?

Again, thanks for sharing your knowledge!
Find all posts by this user
Quote this message in a reply
09-29-2022, 01:41 PM
Post: #7
RE: How do I create a Subroutine using System RPL?
(09-28-2022 10:10 PM)wmundstock Wrote:  I am using Debug4X.

Debug4x is still a very capable system, albeit dated and reminiscent of the early days of a Delphi IDE in many ways. Even with all of its warts, I dread the day that I can no longer use it.

(09-28-2022 10:10 PM)wmundstock Wrote:  How would one do a subroutine in a regular program that is not installed as a library?

In that situation, everything needs to be encapsulated into a single object. You would essentially do this the same way you would do it in a UserRPL program: put the subroutine on the stack, then assign it to a local variable at the beginning stages of your code. When you need to call it in your program, you simply recall it to the stack and execute it with EVAL.

I'll put together an example and post it to this thread separately.

(09-28-2022 10:10 PM)wmundstock Wrote:  I have a final question here, how would one learn about the REALREAL thing? I found references of it in the document Programming in System RPL Second Edition, but nothing self explanatory. Is there another documentation that you recommend?

The document you mentioned above is the best reference I know of (there may be others in different languages floating around that I don't know about, however). Regarding the dispatch mechanism, I think it suffices to know the syntax, then look for supported BINTs that have the value you need. That same document has a (mostly complete) list of the known supported and not-supported-but-in-a-safe-address-range BINTs that can be used for any purpose (dispatch or otherwise). Not all BINTs are available as objects in ROM, so you will inevitably need to use the "# <hex value>" format at times.

Much of the learning process for System RPL programming is simply trial-and-error. Go through some of the examples in that document you mentioned, and give yourself increasingly challenging exercises. Once you start becoming comfortable with the coding basics, you'll start to become more adept at seeing how the pieces work together by looking at other's code.

One of the best teachers for that is to install Nosy and an extable on an emulated 50g, which then allows you to see the actual coding techniques used by the developers of the platform. I don't recommend starting out with that, though, as there are definitely some advanced techniques that won't make much sense until you've got a good handle on the underlying principles of runstream control, meta manipulation, and loop constructs. Those techniques are used quite a bit in ROM, so I definitely recommend becoming familiar with the basics before trying to decipher what's built-in.
Find all posts by this user
Quote this message in a reply
09-29-2022, 02:45 PM
Post: #8
RE: How do I create a Subroutine using System RPL?
(09-29-2022 01:41 PM)DavidM Wrote:  In that situation, everything needs to be encapsulated into a single object. You would essentially do this the same way you would do it in a UserRPL program: put the subroutine on the stack, then assign it to a local variable at the beginning stages of your code. When you need to call it in your program, you simply recall it to the stack and execute it with EVAL.

I'll put together an example and post it to this thread separately.

Don't worry about the example, I have found an example of that approach. I made an update to my question just a few minutes ago. Thank you though! I was just wondering if there was an easier/simpler way to accomplish the same.
Find all posts by this user
Quote this message in a reply
09-29-2022, 04:38 PM
Post: #9
RE: How do I create a Subroutine using System RPL?
(09-28-2022 10:10 PM)wmundstock Wrote:  I found the following example, but seems rather overly complicated for something simple.
Code:
RPL 
( C:\Users\User\Documents\HP Projects\SystemRPL\NonLibrarySubroutines\CMP.s, part of the NonLibrarySubroutine.hpp project, created by <> on 29/09/2022 )

* Main program starts here
::

    ' ( puts next object into the stack, in this case some short code )
    ::
       "Equal are the values"
    ;
    
    ' ( puts the second function to the stack )
    ::
       "Different are the values"
    ;
    
    ( Name each one of the LAMS as below. )
    { LAM ValuesAreEqual
            LAM ValuesAreDifferent }
            
    BIND ( This "stores the LAMs - creates temporary context )
    ( Carry on with your MAIN program )
    CK2&Dispatch
 REALREAL ::  ( note: REALREAL is in ROM and defined as #00011 )
    %= ITE
       LAM ValuesAreEqual ( this is how to call a LAM )
       LAM ValuesAreDifferent
 ;
        ABND ( Purge the temporary variables ).
        
;

There are a couple of structural problems above. I'll try to summarize.

The code word CK2&Dispatch should only be used in a library command, and it should be the first item in the code (after the initial ::, of course). This example is, by definition, a stand-alone program instead of a library command. So instead of CK2&Dispatch, you would use two separate code words:

CK2NOLASTWD
CK&DISPATCH1

Generally speaking, you start a program with the argument check/dispatch tools, then you proceed with setting up locals and validating argument applicability. I usually validate the arguments before setting up locals, unless the locals being defined would make it simpler to validate things. Forcing an early exit by raising an error will normally take care of an active temp environment, but it has to be marked appropriately for that to work. That marking is done with the CK<n>NOLASTWD command, which is but one reason that needs to be first in the code.

Here's how I would change your program to follow this flow:
Code:
RPL

INCLUDE DirMacro.s

ASSEMBLE
    Dir <CMPprog>
RPL
::
   CK2NOLASTWD
   CK&DISPATCH1
   REALREAL ::

      ( validate the arguments [none needed here] )
      ( if the reals needed to be in a certain range, or positive, or have
        no fractional part, or ..., this is where we would check those values )

      ( subroutine - values are equal )
      ' ::
         "Equal are the values"
      ;

      ( subroutine - values are not equal )
      ' ::
         "Different are the values"
      ;

      ( establish locals )
      {
         LAM ValuesAreEqual
         LAM ValuesAreDifferent
      }
      BIND

      ( main program - check for equality )
      %= ITE
         LAM ValuesAreEqual
         LAM ValuesAreDifferent

      ( abandon the LAMs )
      ABND
   ;
;

This is fine for an example program. You'll eventually want to explore null-named LAMs (instead of the named ones like we've used here). You can still give them names in the source code using DEFINE statements, but they are more memory efficient and measurably faster in a running program. The only time I use named LAMs is when I can't predict in advance if there will be intermediate temporary environments in existence later in the running program. Their existence would have the consequence of renumbering the null-named locals, so it would be difficult to deal with the renumbering without a lot of cumbersome code.

(Note: the inclusion of DirMacro.s and the "ASSEMBLE..RPL" header allow you to create a directory with multiple programs/objects -- see the example named "Direct" in the Debug4x Examples folder in your My Documents folder for more info)
Find all posts by this user
Quote this message in a reply
09-30-2022, 02:24 PM
Post: #10
RE: How do I create a Subroutine using System RPL?
Hi David, thank you very much for spending the time teaching me this stuff. You already provides way more informaiton than I expected! Greatly appreciated!

A few more notes and questions on your comments.

(09-29-2022 04:38 PM)DavidM Wrote:  The code word CK2&Dispatch should only be used in a library command, and it should be the first item in the code (after the initial ::, of course). This example is, by definition, a stand-alone program instead of a library command. So instead of CK2&Dispatch, you would use two separate code words:

CK2NOLASTWD
CK&DISPATCH1

This is very interesting. I did note the error XLib 695 49 Error: Too Few Arguments and the XLib keword caught my attention. After your note I did a quick search on the Programming in System RPL Second Edition and its all there.

(09-29-2022 04:38 PM)DavidM Wrote:  Generally speaking, you start a program with the argument check/dispatch tools, then you proceed with setting up locals and validating argument applicability. I usually validate the arguments before setting up locals, unless the locals being defined would make it simpler to validate things. Forcing an early exit by raising an error will normally take care of an active temp environment, but it has to be marked appropriately for that to work. That marking is done with the CK<n>NOLASTWD command, which is but one reason that needs to be first in the code.

That is a great coding practice, thanks for calling that out, it makes total sense to me.

In addition, the same section of the document also describes this behavior and the "marking" that you refer to.

My understanding is that right after the CK&DISPATCH1 we add the types we want to check. If its real real it should be #00011 so this mean we can only test up to 5 arguments for their type?

What if we had a CKNNOLASTWD with N=10? Would I create a loop to check each parameter individually?

I tried using the directory macro as you suggested. Does that only work if in one single file? I tried creating 2 files and assignign a section like below, but it did not work.

File 1: Named.s
Code:
INCLUDE DirMacro.s

ASSEMBLE
    Dir <NAMEd>
    
RPL
File 2: Unnamed.s
Code:
INCLUDE DirMacro.s

ASSEMBLE
    Dir <UNNMD>
    
RPL

After moving both Assembles into one file it worked.

Thank you!
Walter
Find all posts by this user
Quote this message in a reply
09-30-2022, 03:55 PM
Post: #11
RE: How do I create a Subroutine using System RPL?
(09-30-2022 02:24 PM)wmundstock Wrote:  My understanding is that right after the CK&DISPATCH1 we add the types we want to check. If its real real it should be #00011 so this mean we can only test up to 5 arguments for their type?

What if we had a CKNNOLASTWD with N=10? Would I create a loop to check each parameter individually?

The standard dispatch mechanism supports checking up to 5 arguments. As you've noted, there is the "CKN..." structure, but that is most useful when you've got a number in stack level 1 that is indicating a count of things to operate on (the example of DROPN is a good one to describe that situation).

If you need to check more than 5 arguments, you have to do it a different way. The SysRPL DEPTH code word can be useful (after a preliminary check for the first 5 using standard commands). It's possible to "stack" the dispatch calls (CK&DISPATCH1), but you shouldn't try to stack CK5NOLASTWD with the other "counting" checks (CK<1..5>NOLASTWD).

This all becomes rather messy, and I would actually recommend that you reconsider whether passing that many arguments on the stack is the best approach. If you really need that many arguments, it's probably better to pass them in list form to the program. That allows you to use the standard count/dispatch mechanism, but then requires that you do a manual check of the number of elements in the list and their types. That's not so difficult, really, and I would suggest that the code would look more organized if you went that route instead.

(09-30-2022 02:24 PM)wmundstock Wrote:  I tried using the directory macro as you suggested. Does that only work if in one single file? I tried creating 2 files and assignign a section like below, but it did not work.
...
After moving both Assembles into one file it worked.

You answered your own question here. Smile The objects you're creating need to appear to the compiler as if they are all in 1 file due to the way DirMacro works. This can be done one of two ways:
  1. The objects being defined need to all be in one source code file. This is essentially what you did above.
  2. The object definition source can be in separate files, but there would still need to be a single file that brings them all together with INCLUDE compiler directives for each separate source file.
Imagine you have 3 separate source files containing definitions for 1 object each:

file: object1.s
Code:
ASSEMBLE
   Dir <ob1>
RPL
::
   % 123
;

file: object2.s
Code:
ASSEMBLE
   Dir <ob2>
RPL
::
   % 456
;

file: object3.s
Code:
ASSEMBLE
   Dir <ob3>
RPL
::
   % 789
;

To compile these into a directory object that has all three, you would then need to have a "master directory" source file that has something like the following:
Code:
RPL

INCLUDE DirMacro.s

( object 1 )
INCLUDE object1.s

( object 2 )
INCLUDE object2.s

( object 3 )
INCLUDE object3.s

Note that the INCLUDE for the DirMacro only exists in the "top level" file. There should only be 1 INCLUDE for that macro in the hierarchy.

Hope this helps!
Find all posts by this user
Quote this message in a reply
09-30-2022, 05:05 PM
Post: #12
RE: How do I create a Subroutine using System RPL?
(09-30-2022 03:55 PM)DavidM Wrote:  Hope this helps!

You have been super helpful! Thank you, again, for takig the time to answer my questions.

In the process of trying Named and Unnamed LAMs I wanted to display a message. I was sucessful using FlashWarning, but I had issues with the FlashMsg.

From documentation FlashMsg should display a message in the status area, it seems to clear that out but it does NOT display the message.

This does not work.
Code:
::
    "Hello World"
    FlashMsg
;

As per documentation, FlashMsg implicitly calls DISPSTATUS2, so I tried code below which works.
Code:
::
    "Hello World"
    DISPSTATUS2
    SetDA1Temp
;

I used Nosy to check what FlashMsg does, and here it is:
Code:
::
    Save16Patch_
    SWAP
    PTR 11C27->DISPSTATUS2
    BINT80
    ZERO_DO
    SLOW
    LOOP
    Rest16Patch_
;

Documentation does not list the entry Save16Patch but seems like it saves the screen as a grob. Then it does a SWAP which would bring my string back to level 1 and then it does the DISPSTATUS2.
The rest of the code seems to be a simple Sleep to wait some time before moving on. The the Rest16Patch will then restore the previous screen.

Not sure what could be wrong here. Any suggestion?
Find all posts by this user
Quote this message in a reply
09-30-2022, 06:53 PM
Post: #13
RE: How do I create a Subroutine using System RPL?
(09-30-2022 05:05 PM)wmundstock Wrote:  ...From documentation FlashMsg should display a message in the status area, it seems to clear that out but it does NOT display the message.

This does not work.
Code:
::
    "Hello World"
    FlashMsg
;
...
Not sure what could be wrong here. Any suggestion?

For someone just starting out with SysRPL, you are definitely getting a good start! Explorations like this one are common, as there are many situations like this where things seem different than you might expect.

You are on the right track, and using Nosy to check the workings of the command is (for me, at least) a common tactic when troubleshooting something broken that seems like it should be working.

I'll go out on a limb here and guess that you only tried the original program on an emulated calculator. If you had tried it on a real calculator, I believe you would find that it worked as expected. Looking at the internal code of the FlashMsg command provided the clue that was needed.

First: Save16Patch_ saves the top 16 rows of pixels as opposed to the entire display. You were definitely on the right track with that.

The FlashMsg command is actually working, but the "pause" section you identified is executing so quickly (unlike on a real calculator) that you simply don't see the string. Even 80 iterations of SLOW on an emulated calculator is soooooo fast that you never see the string before it is obliterated with Rest16Patch_. The reason is because the SLOW, VERYSLOW, and VERYVERYSLOW commands don't look at clock ticks, but rather perform a simple loop at the Saturn code level (the loop count varies depending on calculator model). The emulator steps through the loop much faster than a real calculator, even if set for "Authentic Calculator Speed".

There's no reason you would have known this kind of thing, but it's certainly part of the learning process for SysRPL development on emulated calculators based on Emu48. This is from the "Problems.txt" file which is included with Emu48:
Quote:- System-RPL commands VERYVERYSLOW, VERYSLOW and SLOW depends on PC speed (are realized as simple down counter in ROM)

I believe you identified the best course of action. This is simply a situation where you have to make choices depending on the intended platform for the final app. If you only need it working for an emulator, then you'll have to use some alternative for pausing.
Find all posts by this user
Quote this message in a reply
09-30-2022, 08:03 PM
Post: #14
RE: How do I create a Subroutine using System RPL?
(09-30-2022 06:53 PM)DavidM Wrote:  For someone just starting out with SysRPL, you are definitely getting a good start! Explorations like this one are common, as there are many situations like this where things seem different than you might expect.

You are on the right track, and using Nosy to check the workings of the command is (for me, at least) a common tactic when troubleshooting something broken that seems like it should be working.

Thank you! It helps to know at least I was in the right path. I was using Nosy as per your earlier suggestion. Took me a little while to understand how it works but it is a great tool for learning.

(09-30-2022 06:53 PM)DavidM Wrote:  I'll go out on a limb here and guess that you only tried the original program on an emulated calculator. If you had tried it on a real calculator, I believe you would find that it worked as expected. Looking at the internal code of the FlashMsg command provided the clue that was needed.

First: Save16Patch_ saves the top 16 rows of pixels as opposed to the entire display. You were definitely on the right track with that.

The FlashMsg command is actually working, but the "pause" section you identified is executing so quickly (unlike on a real calculator) that you simply don't see the string. Even 80 iterations of SLOW on an emulated calculator is soooooo fast that you never see the string before it is obliterated with Rest16Patch_. The reason is because the SLOW, VERYSLOW, and VERYVERYSLOW commands don't look at clock ticks, but rather perform a simple loop at the Saturn code level (the loop count varies depending on calculator model). The emulator steps through the loop much faster than a real calculator, even if set for "Authentic Calculator Speed".

There's no reason you would have known this kind of thing, but it's certainly part of the learning process for SysRPL development on emulated calculators based on Emu48. This is from the "Problems.txt" file which is included with Emu48:
Quote:- System-RPL commands VERYVERYSLOW, VERYSLOW and SLOW depends on PC speed (are realized as simple down counter in ROM)

I believe you identified the best course of action. This is simply a situation where you have to make choices depending on the intended platform for the final app. If you only need it working for an emulator, then you'll have to use some alternative for pausing.

Amazing! You guessed right and I was just trying it in the emulator.
It would probably take me some time to realize that! Interestingly there is a checkbox in the emulator that says "Authentic Calculator Speed" which is checked, but don't seem to have an effect on these particular commands.

I did try to use Nosy to see what's in the SLOW command, but it got down to assembly level. It seems like a loop of some sort, but I cannot follow that execution. I think that is too far for me for the time being. I found another document about the saturn assembly but I think I have to spend more time on System RPL level...
Find all posts by this user
Quote this message in a reply
Post Reply 




User(s) browsing this thread: 1 Guest(s)