manual:chapter5:basics

This is an old revision of the document!


RPL provides a programmable environment to do numeric and symbolic calculations. Programs in newRPL are sequences of commands that operate on objects. The main temporary storage for objects is the stack. Objects enter the stack always at level one, displacing other objects to higher levels. Most commands and operations take objects from the stack, operate on them, and then place the result as a new object in the stack. A typical sequence of a program is to place some objects on the stack, do one or more operations and leave the result in the stack. For example:

« 1 2 + »

Let's analyze the program above:

All newRPL programs are enclosed between « and » delimiters. They serve no purpose other than define the beginning and end of a program (or a section of a program, as they can be nested). The delimiters also define the life span of local variables (more on that later). First the program has the objects 1 and 2. The object 1 executes first, placing itself on stack level 1. The number 2 then executes, placing itself on stack level 1, relegating the number 1 to stack level 2. At this point, the numbers 1 and 2 are on the stack, and the operator + is executed. This operator takes 2 arguments from the stack, performs a sum, and leaves the result on the stack. The program reaches the end, leaving the number 3 on level 1 of the stack (the numbers 1 and 2 were removed by the operator +). And that's the most basic RPL program. Operators and functions always take some values from the stack and leave some results on it. A program may include multiple operations:

« 1 2 + 2 / »

The program above starts identical to the previous example, and after the + operator is executed, the resulting number 3 is left on the stack. As the program continues, the number 2 is put on level 1, relegating the number 3 to level 2. The division operator takes 2 numbers and performs the division of the number in level 2 divided by the number in level 1. In this case, level 2 had a 3 and level 1 had a 2, therefore the number 1.5 will be left on the stack as a result.


In the examples above we showed how + and / would take some objects from the stack, operate on them and place the result in the stack. This is the basic operation of all operators and commands in RPL. All commands and operators take some (or none) arguments from the stack, and leave some (or none) results in it. There are hundreds of RPL commands, all based on the same principle of taking and leaving objects on the stack. Future chapters will describe each command in depth, for now only a few commands will be introduced that define the basic core of the RPL language. The basic arithmetic operators work as expected (+ and / were already introduced), taking objects from the stack and leaving the result of the operation. Test operators (>, <, ≤, ≥, == ) take two objects from the stack, perform a comparison and leave a 1 on the stack if the test was true, and a 0 if the test failed. The true condition is always represented with the number 1, while the false condition is always represented with the number 0.


Once the program of the previous example is entered on the command line, and after pressing ENTER, the program will be put on the stack (on level 1).

To execute the program, simply press EVAL. The command EVAL takes one object from the stack and evaluates it, leaving the result on the stack. More on what evaluating an object means will come later, for now it suffices to say that if the object is a program, evaluating it will execute the program.

Notice that the example above can be introduced in the command line without the « » delimiters. In such case, it's not a program but an immediate sequence of operations that the user wants calculated, so it's executed immediately, and not put on the stack.

Using the delimiters, the sequence is turned into a program that can be preserved to be executed multiple times later.


Programs may take values from the stack, just like any RPL operator or command. To use values from the stack, a program simply needs to assume they are there and operate on them. Our previous example could be rewritten as:

« + 2 / »

This short program runs the operator + as its first command. Previously, we had included in the program the numbers 1 and 2, which were put on the stack. This version now expects the user to leave two numbers on the stack before running this program. The program will then add them together, and later divide the result by two (computing the average).


The usefulness of programs like the previous example is fully apparent if we can give the program a name and store it permanently, rather than type it every time. If we were to name this program AVERAGE, for example, we could use it every time we need to compute the average of two numbers, by putting the numbers on the stack and running this program. This can be achieved with variables.

Variables are named containers that can hold objects of any type. A variable is said to be “global” when it can be accessed by any program or the user, and “local” when it can be accessed only by the program that created it and its subprograms.

Variables are created with the comand STO (store) which stores an object into a named variable.

« 4 'X' STO »

The program above stores the number 4 in a variable named X. Notice the name of a variable must be enclosed in single quotes when it refers to the name itself, and without quotes when it refers to the content of a variable. In this case, we want the name 'X' to be put on the stack, to be used by the STO command.

After running this program there's no result on the stack, since STO does not put anything back on the stack. But now the variable X was created with the value 4, and can be used:

« X 1 + »

Notice the X is unquoted this time, so this program will put the contents of variable X (the number 4) on the stack, then the number 1 and then add them together, leaving a 5 as a result.

The variable X is global and can be accessed always from now on. Typing X and enter will put a 4 on the stack.

To remove the variable, use the PURGE command:

'X' PURGE

Back to the average program, we can store it and give it a name:

« + 2 / » 'AVERAGE' STO

The sequence above is not enclosed in delimiters, so it will be executed immediately on the command line. First it puts the program on the stack, then the name 'AVERAGE' and finally does STO, which stores the program into the variable 'AVERAGE'.

Now the user can type:

3 5 AVERAGE

and the result 4 will be left on the stack. It can also be used inside a program:

« 3 5 AVERAGE »

will do the same when executed.

To make it even more useful, variables appear in the soft menus, so instead of typing the word AVERAGE every time, simply using the corresponding key will also run the program and compute the average.

3 5 APPS(G) will do the same, assuming there were no other variables, so that AVERAGE was shown on the first spot of the second soft menu, activated with the G key.


Often, it is convenient to have named variables within a program, but these variables only have meaning within the program, and should vanish as soon as the program finishes. These are called local variables, as opposed to global variables.

Creating local variables

Local variables can be created in several ways within a program. For example, a program that calculates the value of the function $\frac{x^2}{x-1}$ can be written as:

« X X * X 1 - / » 'MYFUNC' STO

Now running MYFUNC will calculate our function using the value in the global variable X. If we want to evaluate the function for multiple points, we'd have to previously STOre each value in the variable X, and run MYFUNC every time.

Local variables allow the program to assign names to the arguments from the stack without any modifications:

« → X « X X * X 1 - / » » 'LOCALFUNC' STO

The first operator → creates local variables. After the operator, follows a list of all local names to be defined (in this case just one, X) and after that, the program within which these variables will exist.

The operator expects enough values on the stack to store in all the local variables to be defined. In this case only one value is needed. The program now can be used as:

4 LOCALFUNC

which will return 5.33333, the value of our function valuated at 4.

There's another way to create local variables anywhere within a program, using the command LSTO (for Local STO). The syntax is the same as STO, taking the value and a name from the stack, and creating a local variable.

The LSTO command is a new feature of newRPL. Classic RPL does not allow creation of local variables at arbitrary locations in the code.

Scope of local variables

In general, local variables only exist within the program that defined them, that usually means within the « » delimiters. Other scope delimiters are the immediate secondary delimiters :: ;, and all commands that define a loop.
In newRPL, any reference to a variable name will first look for a local variable in the current program, then in any parent programs, if not found, it will look for a global variable in the current directory, then in any parent directories until HOME.
Because of this priority, a local variable will prevent access to variables with the same name in parent programs, or in the current directory. When this happens it is said that the local variable is casting a shadow over the other ones, or that the other variables are being shadowed by the new local.

Visibility of local variables

When a program calls another program, all its local variables are visible as globals to the inner program. In general, programs should use local variables and not modify global variables unless that's their explicit purpose. A program that uses global variables can be sandboxed by creating local variables with the same names and then calling the program. The sandboxed program won't be able to reach the global variables, and will be forced to modify only the local variables within the sandbox.

A simple example on the scope of local variables:

3 'X' STO 4 LOCALFUNC

In this case, the value 3 is stored in the global variable X, and then the program LOCALFUNC from the previous section is executed with an argument of 4 in the stack. Since the program creates a local variable named X with a value of 4, all references to X within the program refer to that local X, not the global one and therefore it also returns 5.333333.

The user can now check by typing X on the stack or using the menus, that the global variable X still holds its value (3), while the local variable X with the 4 vanished as soon as the program terminated.

Back to the MYFUNC example, which used the global variable X to calculate, a wrapper program can be added as follows:

« 4 → X « MYFUNC » »

This new program will create a local variable X that will “shadow” any globals named X. Since MYFUNC expected a variable X to exist, it will first see the X that was just created, local to the new program (the calling program) and will use it, regardless of whether a global variable X exists or not. This way, the MYFUNC program can be used unmodified without being necessary to disturb the value of the global variable X.

Let's look at the example:

«
   4 'X' STO
   X
   3 'X' LSTO
   X + 'X' STO
   X
»
EVAL
X

Let's analyze it by parts:

4 'X' STO creates a global variable X with the value 4

X refers to the value of the variable X (the only existing at this time is the global X), so it leaves the number 4 on the stack

3 'X' LSTO creates a local variable X with the value 3

X refers to the value of X, but now it will access the local variable X, leaving a 3 on the stack

+ will add 4+3=7

'X' STO will store the result (7) in the variable X. In this case, the local variable X.

X will leave the value of the result X on the stack (7).

At the end of the program, the local X is eliminated and only the global X remains, with the original value of 4, so the last X following EVAL will leave a 4 on the stack, not a 7.

The behavior of STO and LSTO is only slightly different:

Both LSTO and STO will search in the usual priority order (locals first, locals from parent programs second, globals from current directory, then globals from parent directories). If a variable is found during this search, STO will store the value in the existing variable. LSTO on the other hand, will only overwrite an existing variable if it is local and was created in the same program, otherwise it will create a new variable, local to the current program. If the variable doesn't exist: STO will create a global variable. LSTO will create a variable that is local to the current program.
While it may seem complicated at first, the use is quite natural: STO can be used to create global variables, or to store into existing variables (local or global).
LSTO is used to create new local variables, in order to guarantee that existing globals or locals from other programs won't be modified.
Once a local variable is created with LSTO, it is recommended to use STO when subsequently modifying its value. As long as execution remains in the current program either STO or LSTO can be used. However, commands that create loops might create separate variable scopes, and in that case LSTO will create a new local inside the loop. In large programs it may not be obvious that an additional variable scope was created by a loop. It's therefore recommended to create variables outside of loops, and use STO to store values, leaving the use of LSTO for local variable creation exclusively.
The following programs are equivalent:

« 3 'X' LSTO X 1 + 'X' LSTO X »
« 3 'X' LSTO X 1 + 'X' STO X »

However, if execution is in a different program (additional delimiters were added here to achieve that separation):

« 3 'X' LSTO « X 1 + 'X' LSTO X » EVAL X »
« 3 'X' LSTO « X 1 + 'X' STO X » EVAL X »

The example above has a program inside another program. In both cases, a variableX is created first, local to the outer program with a value of 3. In the first case, the inner program will add 1 and use LSTO, creating a new local variable that will exist within the inner « » delimiters only, so the X at the end of the inner program will put a 4 on the stack. The X after the EVAL in the outer program, will only see the original value of 3, as the local X with the 4 was destroyed at the end of the inner program.
In the second case, the inner program uses STO, which will store it in the existing local variable of the outer program instead of creating a new local variable. The X in the inner program will show the value 4, and the one after EVAL will now also be a 4, both referring to the X in the outer program (the only variable that was created).

It was stated above that local variables from a program are always visible as global variables to any programs called within it. This differs from classic RPL, where local variables were not visible unless explicitly requested (compiled local variables). This feature might introduce incompatibilities in old programs that use global variables with identical names as local variables from a parent program. On the positive side, this feature allows both passing arguments through variables and also sandboxing programs to “capture” their attempt to modify global variables.


newRPL has several different constructs to perform loops and to check if a condition is true or false.

Conditionals: IF/THEN/ELSE/END

The syntax for this construct is:

IF test_clause THEN statements END

or with the ELSE part.

IF test_clause THEN statements ELSE statements END

The syntax is IF (something is true) THEN (do this) ELSE (do that) END. Anything between IF and THEN is a test clause, and has to leave a true/false result on the stack. If the result is true, anything between the THEN and ELSE will be executed, and if it's false, anything between ELSE and END will be executed instead.

The ELSE part is optional: IF (something is true) THEN (do this) END is a valid construct, which will execute anything between THEN and END if the condition is true, and simply do nothing if it;s false.

For example, the following program:

« 1 'X' STO 2 'X' LSTO IF X 2 == THEN "YES" ELSE "NO" END »

It begins by storing the number 1 in a global variable X, and the number 2 in a local variable. Now let's focus on the conditional statement. The test clause in this case is X 2 ==, which is true if X equals 2. Since we have just created a local X with the number 2, the condition is true and execution will continue after the THEN statement, leaving the string “YES” on the stack. Anything after the ELSE is skipped (because the test was true) and execution continues after the END statement. Now let's modify the example by reversing the order of the STO operations:

« 2 'X' LSTO 1 'X' STO IF X 2 == THEN "YES" ELSE "NO" END »

In this case, the first LSTO creates a local variable X with the number 2, but the STO operation changes its value to 1. When the test clause is executed, X 2 == is now false (indeed X is 1), therefore anything after the THEN is skipped and execution continues after the ELSE word, leaving the string “NO” in the stack.

Notice that the test result is a true/false value taken from the stack, and doesn't necessarily have to be put on the stack between the IF and THEN statements. For example the following is perfectly valid:

« X 2 == IF THEN "YES" END »

The test clause is outside the construct, which makes it difficult to understand for a human, but the effect is exactly the same as long as a true/false value is on the stack when the IF/THEN statement is executed. This is also valid for loops, where the test clause might be empty.Since putting the test clause in the right place makes for a much easier to read program, it's consider good practice to put the test clause between IF and THEN.

Conditionals: IFT/IFTE

The syntax for this construct is:

test_clause command IFT

or

test_clause commad_if_true command_if_false IFTE

These are simple commands, as opposed to a construct formed by various words. The IFT (IF True) takes 2 arguments from the stack. The argument in level 2 of the stack is a true/false condition, while in level 1, it expects an object to evaluate only if the condition in level2 is true, otherwise it does nothing.

« 1 'X' STO X 1 == "Yes" IFT »

The example above stores 1 in variable X, then X 1 == compares X and 1, leaving true in the stack if they are equal (in this case yes). It then puts on the stack the object to evaluate, in this case the string “Yes” (usually it's a program, but any type of object can be used). The IFT will take the two arguments, and since the condition is true, will evaluate the “Yes” string, leaving it on the stack.

IFTE is similar but takes 3 arguments from the stack. Level 3 has the test condition, level 2 the object to execute if true, and level 1 the object to execute if false. The behavior is similar to IF/THEN/ELSE, where the object in level 1 will only be executed if the condition is false, the object in level 2 only if the condition is true. IFT and IFTE are reverse polish equivalents of IF/THEN/END and IF/THEN/ELSE/END.

Conditionals: CASE/END

The syntax for this construct is:

CASE 
statements 
  @default 'case' clause, there is no test
END
</code>
or
<file>
CASE 
test_clause1 THEN statements END 
@do the statements only if the test_clause1 is true and then
@quit the case block
test_clause2 THEN statements END 
@do the statements only if the test_clause2 is true and then
@quit the case block
... @as many tests as needed with their corresponding THEN and END 
statements @do this since none of the above test clauses were true
  @this is the default clause
END

Similar to the IF statement, there is a series of test clauses with THEN statements END structure. The first test clause is evaluated, and if it's true, anything between the following THEN and END statements will be executed, otherwise it will be skipped and execution will continue at the next test clause. In this way, test clauses will be executed until one of them is true. When this happens, the action specified for that test will be executed (the action is specified immediately after the THEN), and when the END word is reached, execution will skip all other clauses until the END of the CASE statement. If none of the test clauses is true, execution falls trough to the default clause, finished with an END statement.

For example:

« CASE 
  X 1 == THEN "ONE" END
  X 2 == THEN "TWO" END
  "OTHER"
  END 
»

If the variable X contains the value 1, the first test clause will be true, the string “ONE” will be put on the stack and that's the end of the CASE statement.

If the variable X contains the value 2, the first clause will be false, execution will continue to the second test clause, which is true in this case, leaving the string “TWO” in the stack and ending the CASE.

If the variable X contains 0, the first and second test clauses will fail, execution will continue at the default clause, leaving “OTHER” in the stack.

FOR loops

The syntax for this construct is:

startIndex endIndex FOR counterVariable statements NEXT

or

startIndex endIndex FOR counterVariable statements increment STEP

A FOR loop is almost identical to a START loop, except the counter is made available to the program inside the loop as a local variable. For example:

« 1 10 FOR J “HELLO” NEXT » This example is the same as the START example, except that after the FOR statement, we are required to provide the name of a local variable that will become the index counter for the loop. In this case we used the name J (arbitrarily). The example doesn't use J, so it could've been written using a START loop as we previously did. However, the following:

« 1 10 FOR J J NEXT » will count from 1 to 10, on a variable named 'J', and will repeat whatever is after the variable name and the NEXT statement. At the end of the program, the numbers 1 through 10 will be left on the stack, since the second J in the program refers to the counter and will be repeated 10 times with the value of J changing from 1 through 10.

Similar to the START loop, the increment when using NEXT is one, and it can be replaced with the keyword STEP, which takes the increment from the stack:

« 1 10 FOR J J 2 STEP » will leave the numbers 1,3,5,7,9 on the stack.

START loops

The syntax for this construct is:

startIndex endIndex START codeStatements NEXT

or

startIndex endIndex START codeStatements incrementValue STEP

Where startIndex , endIndex are integers. incrementValue is a positive or a negative integer.

START loops execute the codeStatements a number of times equal to the difference between startIndex and endIndex . If one wants to execute with steps, for example from 1 to 100 with steps of 2, therefore doing 1 3 5 7 and so on

This loops takes 2 arguments from the stack: a start and and end count. It repeats anything between START and either NEXT or STEP using a counter that goes from the first argument (start) to the second argument (end). For example:

« 1 10 START “HELLO” NEXT » Will count from 1 to 10, and therefore repeat 10 times whatever is inside the loop delimiters (START/NEXT in this case). As a result, the above program will leave the word “HELLO” 10 times on the stack.

When using the NEXT keyword to end the loop, the counter is increased by one. This can be overriden by using the STEP keyword, which takes the increment from the stack and adds it to the counter:

« 10 1 START “HELLO” -1 STEP » This is equivalent to the previous example, except it counts from 10 to 1, adding -1 at each increment.

The counter in a START loop cannot be accessed from inside the loop.

DO loops

The syntax for this construct is: DO …. UNTIL … END

Unlinke FOR and START, this loop has no counter. It repeats everything between DO and END, as many times as needed until the test clause (which is everything between UNTIL and END) becomes true.

Normally, the test clause will contain some test that leaves a true/false result on the stack. When the word END is executed, it will take one argument from the stack and will verify if it's true or false. If it's true, it will not repeat the loop, if it's false, it will start repeating everything after the DO keyword.

In newRPL, a condition is said to be false if it's the number zero. Anything else will be considered true, including things similar to the number zero, like a (0,0) complex number, or a zero vector [0 0]. Both of them will be considered true.

« 10 'X' LSTO DO X X 2 / 'X' STO UNTIL X 2 ⇐ END » The example above creates a local variable X and then starts a DO loop. In the loop the value of X is left on the stack (the first X) , then halved (by X 2 /) and stored back. This is repeated until the condition X⇐2 is met. At the end of the program, the numbers 10, 5, and 2.5 will be left on the stack. FOR and START are more adequate when there is a counter that is added or subtracted a quantity every cycle. In this example, while X could be seen as a counter of some sort, it is halved on each cycle, so the FOR and START loops can't be used.

WHILE loops

The syntax for this construct is: WHILE … REPEAT … END

The syntax for these loops is WHILE (something is true) REPEAT (this) END. Anything between the WHILE and REPEAT is a test clause, and has to leave a true/false result on the stack. If the result is true, anything between REPEAT and END will be executed, otherwise the loop ends and the program continues at the END statement.

Same as before, a condition is said to be false if it's the number zero. Anything else will be considered true.

These loops are similar to the DO loops with some important differences:

WHILE loops have the test clause at the beginning of the loop, and if the result is false, the loop will not execute at all. DO loops, on the contrary, do one full loop execution before reaching the UNTIL test clause, so even if it results false, the loop was executed at least once. WHILE loops cycle when the clause is true. DO loops cycle when the clause is false. Let's write the previous example using a WHILE loop:

« 10 'X' LSTO WHILE X 2 > REPEAT X X 2 / 'X' STO END » Again, the result will be the numbers 10, 5 and 2.5 left on the stack. Notice that now the loop will execute when X>2. Before, the loop would run until X⇐2 which is a different way of stating the exact same thing.

Since the newRPL is based on the userRPL designed for HP calculator, the basic syntax is shared between those two languages so many examples can be found checking the resources shared by the HP calculator community (in particular for calculators of the series HP 48, 49, 50 ).

Please see the Userful resources page.

  • manual/chapter5/basics.1508221892.txt.gz
  • Last modified: 2017/10/16 23:31
  • by pier4r