08-18-2018, 09:17 PM
(04-06-2018 11:59 AM)Maximilian Hohmann Wrote: [ -> ]I would consider myself a random person in that respect (*), but I have so far failed to understand even a single program written in RPL. I don't know if I really can't understand it or if it is because I don't want to... because whenever I see anything like <<DUP ROT ... I instantly feel the urge to zap it away, just the way I would do with commercial breaks on TV or whenever a hip hop song is played on the radio.
I can give you a walkthrough of the following program for the HP-48G:
(08-18-2018 04:41 PM)Thomas Klemm Wrote: [ -> ]( mm dd yyyy -- dow )
Code:
« ROT
IF DUP 3 <
THEN 12 + SWAP 1 -
ELSE SWAP
END
DUP 100 MOD
SWAP 100 / IP
→ q m K J
« { "Saturday"
"Sunday"
"Monday"
"Tuesday"
"Wednesday"
"Thursday"
"Friday" }
q
m 1 + 2.6 * IP +
K + K 4 / IP +
J 4 / IP + J 2 * -
7 MOD
1 + GET
»
»
To make you familiar with the context please read my original post.
Stack Commands
The aforementioned commands DUP, ROT, SWAP are well know among RPN programmers but we use different names:
DUP is ENTER but without disabling stack lift.
ROT is R↑ but for a 3-level stack.
SWAP is X<>Y.
OVER is RCL Y if you know the HP-41.
DROP is something like CLX followed by R↓.
All these commands stem from Forth so it's not something specific to RPL.
Stack Diagrams
There's an infinite stack (well not really) with RPL. This is nice: we can push stuff on it and don't have to care until later. However we don't have automatic copy of T. The stack may be empty. This is what we get with the DEL command.
You may be familiar with stack diagrams that show the state of the stack with each command.
For instance to calculate: \(3\times(4 + 5)\)
Key X Y Z T
3 3
ENTER 3 3
4 4 3
ENTER 4 4 3
5 5 4 3
+ 9 3
× 27
But with Forth the order of the stack is reversed in diagrams. The top of stack is the rightmost element. But that's exactly the order you enter the data.
So on a HP-48G you can use the ENTER key to separate numbers:
3
ENTER : 3
4
ENTER : 3 4
5
ENTER : 3 4 5
+ : 3 9
× : 27
Or then you can use a space to separate numbers:
3 4 5
+ : 3 9
× : 27
Input
( mm dd yyyy -- dow )
This is just the order we fill the stack before calling the function that returns dow, the day of week.
For today (i.e. August, 18th 2018) we would use:
8
ENTER
18
ENTER
2018
Or then:
8 18 2018
In both cases we end up with the following stack diagram:
8 18 2018
Initialisation
We have to modify the month and year in case of January and February and then calculate both the year of the century and the zero-based century.
First we bring the month mm to the top of the stack:
Code:
ROT @ dd yyyy mm
Now we check if that value is smaller than 3.
Contrary to RPN the commands always consume the parameters even in case of comparisons.
Since we need the value later we have to duplicate it first:
Code:
IF DUP 3 <
Here are the steps in slow motion:
Code:
IF @ dd yyyy mm
DUP @ dd yyyy mm mm
3 @ dd yyyy mm mm 3
< @ dd yyyy mm 0|1
The result (either 0 or 1) is then consumed by the IF statement and we branch to the correct case:
Code:
THEN 12 + SWAP 1 -
Let's split that up into multiple lines:
Code:
THEN @ dd yyyy mm
12 + @ dd yyyy mm+12
SWAP @ dd mm+12 yyyy
1 - @ dd mm+12 yyyy-1
The other case is much simpler:
Code:
ELSE @ dd yyyy mm
SWAP @ dd mm yyyy
And then we finish the IF statement with:
Code:
END
We just have to make sure that at the end of both branches the order of the elements is the same.
year of the century
For this we just have to calculate: yyyy MOD 100.
But since we need that value again later we better make a copy beforehand:
Code:
DUP @ dd mm YYyy YYyy
100 @ dd mm YYyy YYyy 100
MOD @ dd mm YYyy yy
zero-based century
We take the integer part of yyyy after dividing it by 100:
Code:
SWAP @ dd mm yy YYyy
100 @ dd mm yy YYyy 100
/ @ dd mm yy YY.yy
IP @ dd mm yy YY
Local Variables
The next line creates a new context for local variables.
They are assigned in the same order that they appear on the stack.
Code:
→ q m K J
Thus we end up with:
q = dd
m = mm
K = yy
J = YY
The context is marked with these guillemets:
Code:
«
…
»
This step consumed the stack completely so it's now empty.
List of Weekdays
We push that list now but we need it only later:
Code:
{ "Saturday"
"Sunday"
"Monday"
"Tuesday"
"Wednesday"
"Thursday"
"Friday" }
Calculating the Zeller's congruence
The next steps should be easy to understand since it's exactly how you'd calculate the expression on any RPN calculator:
\(h=\left(q+\left\lfloor {\frac {13(m+1)}{5}}\right\rfloor +K+\left\lfloor {\frac {K}{4}}\right\rfloor +\left\lfloor {\frac {J}{4}}\right\rfloor -2J\right){\bmod {7}}\)
Code:
q
m 1 + 2.6 * IP +
K +
K 4 / IP +
J 4 / IP +
J 2 * -
7 MOD
This leaves us with the following stack diagram:
weekdays h
Mapping to Day of Week
Since the index of lists start with 1 we have to adjust that:
Code:
1 + GET
I assume that you can figure out by yourself what GET does.
Next Steps
Debugger
I highly recommend to run the program in the debugger and single step through it. So you can follow the changes of the stack with each step.
Control Structures
There are other control structures that you could explore: CASE, START, FOR, DO, WHILE
List Operations
There's a good reason this language has Lisp in it's acronym. It's worth to make you familiar with lists and their powerful operations.
It allows to calculate Gauss's shoelace formula with just a few lines:
Code:
«
DUP HEAD +
2 « CROSS » DOSUBS
∑LIST ABS 2 /
»
Kind regards
Thomas