Python to RPN
Help & User Guide
Author: Andy Bulka
Revision: 1.09
A revolution in programming an ancient device!
To allow the HP42S to be programmed by a modern, cutting edge, high level language (Python 3). Whilst basic Python syntax is supported, you still have to call HP42S uppercase functions from your Python code to access calculator functionality.
Python is compiled/converted into RPN which you can paste into the famous Free42 calculator emulator, convert to raw files and send via USB cable to your DM42 calculator, or type 😅into your HP42S).
Contents
Quick start - Example demo program
toPOL (the Python name for →POL)
toREC (the Python name for →REC)
Global Scope and the LBL command
Embedded mode - the easy default
Non embedded mode - save space
Removing old programs from the 42S
Tutorial: Fun with Matrices, NumPy syntax and the HP42S
Version A. Desktop Python using Numpy
Here is the full Python to RPN listing
Visualising the matrix on the HP42S using pixels
Background Briefing: What is really going on with this code translator
How to call 42S library functions
Desktop Python vs this Python RPN translator
The power of Python and the zen of the 42S
Appendix 1: RPN Command reference
Appendix 2 - Unsupported Python built in commands
Appendix 3: Converter flags and register use
RPN Code being generated - unoptimised?
Does my Python also run on Desktop Python?
When you start the converter web page, a demo Python program appears, showing a glimpse of the power of programming the 42S (and possibly the HP41) in Python.
def demo():
total = 0
for i in range(10):
result = calc_something(i, 5)
print('Counter ', i, ' result= ', total)
total += result
print('Final total was: ', total)
def calc_something(a,b):
# adds two numbers then squares them
return (a + b)**2
Which results in a counter displaying calculations on the 42S screen then a final total displayed. If you have a printer or have enabled Free42’s virtual printer on your Mac or Pc (or mobile device) you will see:
You can find more examples below, as well as a step by step guide to pasting generated RPN into the HP41S simulator, Free42 - so that you can get your “python” code to actually run on the 42S.
Here is the quick rundown:
Warning: Do not type in regular Python code that you find on the internet or in a book and expect it to be supported. You are programming an ancient calculator. There are no string interpolations in your print statements. There are no python built in functions at all e.g. no input() or raw_input(), no math or random or anything! The only Python you get is the basic (but rather powerful) syntax below - everything else needs to be a function call to a real HP42S function.
For example to calculate sine, you need to call the HP42S SIN function not Python math.sin(). You need to put parenthesis around all Python calls e.g. SIN(90) and provide a Python variable or literal number as a parameter. There is no stack referencing in Python - so you assign the result to a variable e.g. result = SIN(90).
Look at the examples for more detail
And won’t be
Since everything is now a python variable - the stack is of no importance and there is no need to address it in Python. Just use Python. However the generated RPN uses the stack extensively to:
Here is a simple program that adds two numbers together:
def adder(a, b):
return a + b
The output is:
00 LBL "adder"
01 STO 00
02 RDN
03 STO 01
04 RDN
05 RCL 00
06 RCL 01
07 +
08 RTN
This program is ready to paste into Free42.
Here is a for loop. Remember in Python a for loop starts at the beginning number and excludes the finish number.
for i in range(1, 10):
VIEW(i)
00 0.009
01 STO 00
02 LBL 00
03 ISG 00
04 GTO 01
05 GTO 02
06 LBL 01
07 RCL 00
08 IP
09 VIEW ST X
10 GTO 00
11 LBL 02
Notice if the python is not wrapped in a def, so no global label is created. The code fragment may still be useful to you when building up a larger program. You can omit line numbers when generating,
Reference
Whilst you can’t have comments in RPN, the converter will optionally generate comments so that you can understand the registers being used for which variable etc.
LBL "demo"
0
STO 00 // total
-1.009
STO 01 // range i
LBL 00 // for
ISG 01 // i
GTO 01 // for body
GTO 02 // resume
LBL 01 // for body
RCL 01 // i
IP
5
XEQ A // calc_something()
STO 02 // result
"Counter "
RCL 01 // i
Turn on the comment checkbox to generate comments in RPN.
Use the “Copy for Free42” button to automatically strip comments and insert line numbers - ready for pasting into Free42.
You can of course have comments in your Python code. E.g.
ALL() # change display to show all digits
And even triple quoted comments E.g.
"""
You want to change the number of registers to about 60
for this code to run on Free42. SIZE 0060 should do it.
Run the demos with:
XEQ "dd1_dra"
XEQ "dd2_dra"
XEQ "dd3_dra"
"""
def dd1_draw_demo1():
FIX(0)
CLLCD()
"The most "pythonic" way is to use 4 spaces per indentation level. The Python interpreter will however recognize spaces or tabs. The only gottcha is you must never mix spaces and tabs, pick one or the other." ref
The Python to RPN converter code editor converts everything you do into spaces - so its impossible to make a mistake. You can even hit TAB to indent your code and the appropriate number of spaces are inserted. SHIFT TAB will similarly de-indent your code.
You can even use two spaces to indent, as long as you are consistent.
Mac | PC | Action |
TAB | TAB | Insert spaces. SHIFT-TAB deindents. |
CMD-D | Ctrl-D | Duplicate line or selected lines |
CMD-/ | Ctrl-\ | Comment line or selected lines (toggles) |
CMD-X | Ctrl-X | Delete current line |
CMD-] | Ctrl-] | Indent line or selected lines |
CMD-[ | Ctrl-[ | De-Indent line or selected lines |
CMD-Z | Ctrl-Z | Undo |
CMD-SHIFT-Z or CMD-Y | Ctrl-Y | Redo |
Many more shortcuts explained here, as part of the CodeMirror documentation. CodeMirror is the editor widget used in Python to RPN.
Python is case sensitive, which is why the new invented Python commands varmenu() and menu() don’t clash with the traditional HP42S functions VARMENU() and MENU().
This applies to variables, too.
All lowercase python variables (recommended) are mapped to RPN registers, in the order that the converter parser sees them. Thus a = 10 and b = 20 will be mapped to registers 00 and 01 respectively.
All uppercase python variables are mapped to named string variables. Thus X = 10 will be mapped to register “X”.
To allow the creation of lowercase or mixed case named HP42S registers from your Python variable names by using the rpn directive “named” e.g.
y = 100 # rpn: named
See Appendix for more information, or look at the “Variables” example in the Advanced Examples section of the Python to RPN converter website.
Design justification: The above lowercase vs uppercase behaviour is a convention that I made up 😯. The design thinking is that python lowercase variables are used most of the time and are local in scope, thus should be as anonymous as possible - which numbered registers are. Mapping to anonymous numbered registers means we aren’t polluting the named register space - we don’t want local variables appearing in our RCL menu. And the final reason is that local variables are possibly involved in nested scopes (where you might have two ‘x’ variables, one in a nested def) - in which case mapping to the same named register ‘x’ simply would not work.
Python functions are translated into RPN labelled subroutines, and calls to those functions are translated into XEQ calls. All parameter passing and return of values is handled automatically (via the stack) - just think of what you are doing in Python and don’t worry about the RPN.
Calls to built in 42S commands can be made by calling uppercase functions. E.g. to call PSE in RPN you call PSE() in python. Notice you have to add brackets - sorry - that’s Python syntax. To pass parameters to built in RPN functions, simply pass in those parameters as normal as parameters to the python function e.g. VIEW(a), where ‘a’ is a python variable.
Traditionally in RPN you can return multiple values from a subroutine by pushing those values onto the stack.
In Python you can return multiple values from a function using the return statement e.g. return c, b+1. These values can then be assigned to multiple variables e.g. a, b = calc(1, 2). Example:
def multret():
a, b = calc(1, 2)
assert a == 100
assert b == 3
print("multiple return values works ok")
def calc(a,b):
c = a * 100
return c, b+1
Various built in HP42S commands also return multiple values and to access those values you will need to use this Python tuple syntax. For example the →POL command which is accessed from Python using toPOL() is called like this:
a, b = toPOL(1, 2)
In python for loops can be made through
for i in range(0, 200):
pass
# or just
for i in range(200):
pass
Which will loop from 0 to 199.
The generated code will allocate a numbered register for ‘i’ and use the ISG construct to control iteration.
If the python range parameters are more than just literal numbers and are expressions or references to variables, then more complex RPN code is generated, including the generation of a helper function which helps calculate the ISG control variable.
I use ISG to implement for loops because I presume this would be faster and more optimised on the 42S. And thus the generated code needs to remove the fractional part whenever referencing the loop variable.
Note that under the hood, for range loops are implemented with RPN's ISG command, which has a max of 1000 due to the ISG ccccccc.fffii format of fff. If you want to loop more than 1000 then use a while loop and your own counter variable. I might add a rpn: comment directive one day to bypass ISG and implement for using while loops automatically.
While loops are a fantastic alternative to for loops. Continue and break are supported.
def while_demo():
# while loop
n = 0
total = 0
while n < 100:
n += 1
total += SIN(n)
print('done', total)
# continue and break are supported
n = 0
while n < 100:
n += 1
if n > 60:
continue
if n == 50:
break
Python:
if isFS(1):
x = 200
CF(1)
Rpn:
FS? 01
GTO 00
GTO 01
LBL 00
200
STO 00
CF 01
LBL 01
Yes this RPN code is not ideally optimised - this is something for the future.
Flags are not used in Python programming - instead, any Python variable can be used to hold a boolean. However if you need to test, set or clear various HP42S flags then use these functions: CF, SF, FS? use isFS and for FC? use isFC. Note these all take the flag number as a parameter.
This example tests if system flag 21 (controls print on/off from within a HP42S/Free42 program) is set.
Note that the HP42S commands FS?, FC?, FC?C and FS?C are not supported becasue they are flow of control altering commands - not relevant in Python programming. Those commands are not even legal Python syntax (no question marks allowed).
Python actually does allow you to specify a string on a line e.g.
"hi there"
which is very compatible with RPN. The Python to RPN converter will allow this valid Python syntax and generate a string RPN command on its own line - which will cause the HP42S or Free42 to clear the alpha register and add that line to the alpha register. Just add an AVIEW() command and hey presto, you have a message!
However this string is limited to 14 characters. In order to append the next string, one would have to specify another line of text beginning with the ├" which is illegal syntax for Python. Thus I have come up with another way to move strings into the alpha register: use the alpha() function, which takes a string of any length and breaks it up for you into the needed RPN lines. See below for more detail.
Python AVIEW() without any parameters will simply emit AVIEW in RPN. The same with CLA() and most other commands.
A few new facilities have been created to remove the drudgery of dealing with text on the 42S, namely the ability to provide multiple parameters to all the alpha commands 'alpha', 'AVIEW', 'PROMPT', 'PRA' as well as the ability to specify strings of any length.
You can even mix string labels and variables - easing the chore of building up messages to the user.
There are also special Python named arguments to alpha commands: append= and sep= see below for documentation.
There is a new Python function called ‘alpha’ which takes a string, and puts that string into the alpha register. Pass in the text string you want to display, it can be as long as you like. The converter will break up the string into chunks as necessary.
Python:
alpha("Hello there all is well in London!!")
generates the following RPN:
"Hello there al"
├"l is well in L"
├"ondon!!"
Here is an even nicer way to build up alpha register strings in Python
n = 100
alpha("Ans:", n, "and", n)
AVIEW()
RPN:
100
STO 00
"Ans: "
RCL 00
AIP
├" and "
RCL 00
AIP
AVIEW
Which results in
How cool is that?
Is a synonym for AVIEW().
Normal string assignment to variables, long strings can be specified but only six chars make it into whatever variable
VIEW - Normal VIEW, specify Python variables only. Viewing stack or indirect not supported. Does not support multiple parameters.
AVIEW - Same as built in, except takes parameters.
No params is allowed, which will insert a single AVIEW, just in case you have something in the alpha register ready to show. Can specify strings and numbers and variables.
alpha - Builds strings in the alpha register, typically takes parameters.
print - synonym for AVIEW
PROMPT - Same as built in, except takes parameters. See AVIEW re taking multiple parameters.
CLA - Supported, same as RPN.
AIP - Append Integer part of x to the Alpha register.
ASTO - Stores six chars from the alpha register into any Python variable.
ARCL - Same multi parameter features as alpha, except always appends to the alpha register. Must specify variables and literals, not the stack.
for loops and number formats
PRA Same multi parameter features as alpha, adds PRA for printing alpha register to printer/virtual printer.
PRX Prints a variable to the virtual or real printer. Nothing to do with the alpha register. E.g. PRX(myvar), and even matrices are printed.
A “variable menu” is a technique for displaying a HP42S menu containing the names of several variables. See page 92 of the HP42S manual. You can then take your time storing values into those variables, and reviewing them, using those same menu buttons (no need to use STO) - then press R/S to continue into the calculation. The Python to RPN converter supports the traditional native commands needed to use this handy facility, as well as supporting an easier way using varmenu().
def area_app():
varmenu("length", "width") # new easy technique
return length * width
The simplified varmenu() technique allows you to specify all the variables in the one function call. Then in the generated RPN, “boilerplate” calls to MVAR, VARMENU, STOP and EXITALL are automatically inserted for you.
Traditional VARMENU Technique:
def area_traditional(): # traditional technique
MVAR("length")
MVAR("width")
VARMENU("mvadd")
STOP()
EXITALL()
return length * width
The "programmable menu" feature of the HP42S lets you create your own menu - pressing the key underneath each menu item will execute the associated function. There is both a traditional and new simplified way of building these menus.
The new simplified menu() function takes a list of string menu items - representing the function names you want to call. The assigning to keys and all other wiring will be done automatically.
def ui1():
menu("hello", "func2", "add") # new easy menu technique
def hello():
print('you chose hello')
def func2():
print('you chose func2')
return 50
def add(a, b):
return a + b
You must of course define the user functions being called, which by default will be converted into local labels A-J, a-e, and thus won't pollute the global namespace. If you want one of your called functions to be a named label, then as usual, use the comment directive rpn: export on the function definition 'def' line.
If you want to pass parameters to your functions then key them in onto the stack before pressing the menu button. For example to satisfy the call to add(a, b) type 10 ENTER 20 before pressing the menu button for add. The number 10 will arrive as the parameter 'a' and 20 will arrive as the parameter 'b'.
Traditional MENU technique
If you want to build HP42S the traditional way you can do so thus:
def ui1_traditional(): # traditional technique
CLMENU()
"Do A"
KEYG(1, "do_a")
"B"
KEYG(2, "do_b")
MENU()
def do_a(): # this will turn into local label A
print('you chose blah1')
def do_b(): # this will be a named label - rpn: export
print('you chose blah2')
The traditional approach is more laborious, but gives you the advantage of being able to call the menu items something different than the name of the function e.g. notice above that the menu item "Do A" calls the function do_a.
With the traditional approach you may need to clear the menu before you build it - because old MENU definitions hang around till explicitly cleared. The simplified menu()automatically does a CLMENU for you.
Finally, the simplified menu() command implements KEYG semantics, which means each key will run the specified function - this is the behaviour most people expect. If you actually want the KEYX semantics of XEQ then you will need to build the menu in the traditional way, where you get more control and can specify either KEYG or KEYX. P.S. If anybody can point out uses of KEYX, please let me know - I find it most confusing!
Are supported. Implemented in the HP42S as matrices with one column. List items can store numbers and short strings. Example operations:
# List tests
b = [1, 2, 3]
b.append(4)
assert b[0] == 1
assert b[1] == 2
assert b[2] == 3
assert b[3] == 4
assert b[-1] == 4
b[2] = 333
assert b[2] == 333
assert b[-2] == 333
b2 = b
assert b[0] == 1
assert b[-4] == 1
b = [1, 2, 3]
assert len(b) == 3
assert b[-1] == 3
b.pop()
assert b[-1] == 2
# List alpha tests
b = ['a', 'b', 'c']
assert b[0] == 'a'
assert b[-1] == 'c'
b.append('fred')
assert b[3] == 'fred'
assert b[-1] == 'fred'
Not supported:
LIST_UNSUPPORTED = ('cmp', 'index', 'count', 'extend', 'insert', 'remove', 'reverse', 'sort')
del is not supported yet.
Are supported. Implemented in the HP42S as matrices with two columns. Keys and values can store numbers or short strings. Example operations:
# Dictionary tests
d = {1: 11, 5: 22}
assert d[5] == 22
assert d[1] == 11
d[1] = 88
assert d[1] == 88
d[6] = 66
assert d[6] == 66
assert d[5] == 22
assert d[1] == 88
# PROMPT(A[1])
# PROMPT(A[222])
# Alpha Dictionary tests
d = {'first': 11, 'second': 22}
assert d['second'] == 22
assert d['first'] == 11
d['another'] = 33
assert d['another'] == 33
# test dictionary assignment to another variable
a = {'aa': 2, 'bb': 3}
d2 = d # though will create copy not reference - unlike real Python
assert d2['another'] == 33
DICT_UNSUPPORTED = ('clear', 'copy', 'fromkeys', 'get', 'items', 'setdefault', 'update', 'values')
del is not supported yet.
Python to RPN converter supports most of the HP42S matrix commands, but not all are slavishly exposed to Python. An attempt has been made to be more “Pythonic” and use hgher level language constructs where possible, like using simple indexing to access a matrix element e.g.
my_matrix_var[0,2]
is much more convenient and understandable to Python programmers than
INDEX “my_matrix”
1
3
STOIJ
RCLEL
As is apparent in the above example, Python lists/matrices are 0 based whilst HP42S matrices are 1 based, hence the reason why the 0,2 has mysteriously changed into 1,3 in the example above.
Getting back to syntax improvements, for getting and putting sub-matrices, NumPy syntax (the math library for Python) has been used, see reference below on GETM and PUTM.
One more command has been introduced matrix.appendr() which lets you append a row to a matrix, and is a compensation for the lack of GROW and J+ support.
And of course there is len(matrix) which is idiomatic to Python, which can return the number of rows in a matrix - something that the HP42S command set seems to be missing. Knowing the number of rows in a matrix allows for easy iteration through them using nested for loops - see the example in the reference below.
NEWMAT Supported. e.g.
my_matrix_var = NEWMAT(1,4)
DIM Not supported. Instead use the syntax
my_matrix_var.dim(10,10)
to redimension an existing matrix.
You cannot create matrices with the .dim() command.
INDEX Not needed, is inserted automatically where needed
in the generated RPN.
I+, I-, J+, J- Syntactically not even valid Python.
Simply use regular Python indexing and slicing. e.g.
To access a matrix element
result = my_matrix_var[0,2]
To change a matrix element
my_matrix_var[row,2] = 100
PUTM, GETM Replaced with Python NumPy slicing syntax.
For extracting a sub-matrix with GETM use
n = my_matrix_var[0:5, 1:6]
For injecting a matrix into another matrix
with PUTM use
x[2:, 5:] = my_matrix_var or
x[2:n, 5:n] = my_matrix_var where n is the 'to'
P.S. slicing syntax is zero based (HP42S matrices are 1 based)
and is [row_from:row_to, col_from:col_to], 'to' is excluded.
INSR and DELR Replaced with syntax
my_matrix_var.insr(row_num) and
my_matrix_var.delr(row_num)
where the parameter is the row number.
R<>R Syntactically not even valid Python.
Replaced with the syntax
my_matrix_var.row_swap(1,2)
GROW, WRAP Not supported. If you want to grow the matrix
programmatically use m.dim(rows, cols) or
m.insr(at_row_num) or the new command
m.appendr() to append a row (which is all that
GROW, J+ does). Lots of options.
And Python supports len(my_matrix_var) to give
the number of rows so that nested iteration e.g.
can easily replace J+ based iteration. e.g.
for row in range(len(matrix)):
for col in range(5):
m[row,col] = val
INVRT, TRANS,
DET, FNRM
RSUM UVEC,
RNRM Supported, normal calls e.g. m2 = INVRT(m)
DOT, CROSS Supported, normal calls e.g. DOT(m1, m2)
SIMQ Not programmable (HP42S limitation).
Scalar Ops Supported e.g.
x = NEWMAT(1,4)
x *= 3.5
Other Ops Supported e.g.
x = NEWMAT(1,4)
x = SIN(x)
See also the Tutorial: Fun with Matrices, NumPy syntax and the HP42S, later in this help file.
See also complex matrix examples in the help.
Complex numbers and operations are supported.
Native Python syntax for specifying complex numbers is supported.
Takes one or two parameters. If the parameters are two normal numbers (real,imaginary), returns a complex number e.g. complex_num = COMPLEX(0, 1).
Note: You can also enter complex numbers without this COMPLEX function using native Python syntax e.g. 0 + 1j which is engineering notation for 0 + i1. Yes, Python uses the letter 'j' not the letter 'i' and puts it after the imaginary number, not before.
If the parameter is a single complex number, returns two normal numbers - assign the two return values into variables e.g. real, imaginary = COMPLEX(complex_num)
Takes one or two parameters. If the parameter is a single complex number, returns a single complex number e.g. complex_num = toPOL(complex_num). If the parameters are two normal numbers, returns two normal numbers - assign the two return values into variables e.g. a, b = toPOL(1, 2)
Takes one or two parameters. If the parameter is a single complex number, returns a single complex number e.g. complex_num = toREC(complex_num). If the parameters are two normal numbers (θ,r), returns two normal numbers - assign the two return values into variables e.g. a, b = toREC(1, 2)
Command that might result in a complex number
Some operations result in a complex number If you suspect this might happen, use the rpn: named comment directive e.g.
csq = SQRT(-25) # rpn: named
so that the resulting complex number gets assigned to a named variable and not the default, which is a numbered register. Tricky!
def complex_play():
RAD() # Set angular mode
RECT() # Set coordinate mode
FIX(3) # For effective comparisons, need to round down with RND
# Arbitrarily set here so that RND will adhere to 3 decimal places
# Use native Python syntax for entering complex numbers
c = (5 + 3j) + (7 - 9j)
assert c == (12 - 6j)
assert isCPX(c)
# Use HP42 way of entering complex numbers
c = COMPLEX(5, 3) + COMPLEX(7, -9)
assert c == (12 - 6j)
assert isCPX(c)
# Break a complex number into its parts
real, imag = COMPLEX(c)
assert real == 12
assert IP(imag) == -6
# Convert a complex number from RAD to POL
cpol = toPOL(c)
# check result
real, imag = COMPLEX(cpol)
assert RND(real) == 13.416
assert RND(imag) == -0.464
# another way of checking the result
assert RND(cpol) == RND(COMPLEX(13.416, -0.464))
# or more pythonically
assert RND(cpol) == RND(13.416 - 0.464j)
# Convert the complex number back to RAD angular mode
c = toREC(cpol)
real, imag = COMPLEX(c)
assert real == 12
assert IP(imag) == -6
# Some operations result in a complex number
# If you suspect this might happen, use the 'rpn: named' directive
# so that the resulting complex number gets assigned to a named variable
# and not the default, which is a numbered register. Tricky!
csq = SQRT(-25) # rpn: named
real, imag = COMPLEX(csq)
# check result which should be 0 + i5
assert real == 0
assert IP(imag) == 5
# check again
assert RND(csq) == RND(0 + 5j)
# check again
assert RND(csq) == RND(COMPLEX(0, 5))
# to polar and check again
cpol = toPOL(csq)
assert RND(cpol) == RND(COMPLEX(5, 1.571))
assert RND(cpol) == RND(5 + 1.571j)
# To polar can also take two parameters.
# Converts x and y to the corresponding polar coordinates r and θ.
a, b = toPOL(1, 2)
assert RND(a) == 0.464
assert RND(b) == 2.236
# To rect can also take two parameters.
a, b = toREC(1, 2)
assert RND(a) == 1.683
assert RND(b) == 1.081
print('complex tests pass ok')
Are supported. Example:
def matrix_complex_play():
RAD()
RECT()
FIX(4)
m1 = NEWMAT(1,4)
m2 = NEWMAT(1,4)
complex_matrix = COMPLEX(m1, m2)
complex_matrix[0,0] = (5 + 3j) # remember Python matrices are 0 based
val = complex_matrix[0,0]
assert val == (5 + 3j)
assert val == COMPLEX(5, 3)
# Convert complex matrix into two normal matrices
m1, m2 = COMPLEX(complex_matrix)
assert not isCPX(m1)
assert not isCPX(m2)
# Check the contents of the individual matrices
assert m1[0,0] == 5
assert m2[0,0] == 3
See also complex matrix examples in the help.
All the HP42S statistics commands are supported, in the sense that you can call them from Python - but have not been extensively tested. Also, some have been renamed out of necessity - see the RPN command reference table.
Warning: As the statistical data starts by default in Register 11 you will probably need to change this using a call to the StatReg(nn) function because it is likely that long Python programs will eventually allocate variables into this area - remember the Python Converter uses numbered registers 00-nn to store local variable values.
All the statistical functions have been renamed, e.g. CLΣ where it is difficult to type in the unicode Sigma Σ symbol, so the command has been renamed CLStat() etc.
Tip: Accessing the statistical registers
Whilst register use is generally not supported (use Python variables instead) sometimes it is useful to access a specific RPN calculator register e.g. a statistical register like 11 or 12 etc. You can do this with e.g. RCL(12) and this instruction will be inserted into the converted RPN program. Typically entering the Python code x = RCL(12) is more useful since you can then use that value somewhere or even display it e.g. print(x).
Here is an example program which finds the sum of x^2 between numbers _from and _to using the built in feature of the HP statistics function Σ+.
def sumsigma(_from, _to):
CLStat() # CLΣ
for i in range(_from, _to + 1):
StatPlus(i, i) # Σ+
x = RCL(12) # recall summation register
print("sum of x^2 from", _from, "to", _to, "is", x)
Use: On Free42 type
1 Enter 10 XEQ SUMSIGMA
And the answer will appear
And the answer 385 will be left in the X register of the stack.
Doing this on the HP Prime would be
Python has scopes - each function has its own ‘namespace’ or scope, and the area outside functions is known as global scope. References to variables will look in the current scope and search outwards through the scopes till the search reaches the global scope.
You can create a global label for your program using the LBL command. This is an alternative to the default practice of the first function you define becoming a named (global string) main label for your program. This area under the LBL label, and before any 'def' function definitions, is known as the 'global scope'.
LBL("main")
length = 10
width = 20
report()
def report():
print('length is', length, '[LF]width is', width)
By defining a global entry point label in this way, you get to place code and execute code in the global scope area. If you don't define a LBL then the first function you define with 'def' becomes the first string label of your generated RPN program, and you cannot execute code in global scope. This is because on the 42S, execution begins at either a string label generated by a Python function, or at a string label generated by LBL.
One big benefit of using LBL at the top of your program is that you can define variables in the outermost 'global' scope of the Python program, which is outside of any function. These variables will then become accessible inside all functions - often very handy. Use this approach if you want to share variables between functions.
Some scope and function/label rules:
Advanced thought: The LBL technique is a workaround way of executing a Python ‘module’ where the module is akin to the entire Python source code that the Python to RPN converter sees. Normally a desktop Python module is the Python file e.g. myapp.py. With the LBL technique, code that is in the global scope (not inside a function) like variables assignments, function calls, if statements - anything really - can therefore be accessed and executed as if you had typed python myapp.py in desktop python.
Adding rpn: something in a python comment will direct the converter to do different things.
The directive rpn: named will force a Python variable to be mapped to a HP42S register of the same name, instead of being mapped to a numeric register. Use this directive any time you want a a Python variable to be stored in a HP42S lowercase named register mapping instead of the default numbered register.
Normally Python variables are mapped to numeric registers 00..nn in order to avoid polluting the HP42S global variable namespace. However in some circumstances you may wish to expose the variable to look at or use later. This directive forces the converter to allocate a named register.
For example, its useful for the INPUT command to be given descriptive variable names e.g. Normally INPUT(weight) would result in INPUT 00, resulting in a user prompt for 00 which is not really helpful. You could work around this and use the uppercase convention whereby all uppercase Python variables are named resigter thus INPUT(WEIGHT) would result in INPUT "WEIGHT". But if you prefer lower case, you can use the directive and INPUT(weight) # rpn: named which would result in INPUT "weight".
x = 100
y = 100 # rpn: named
INPUT(weight) # rpn: named
Important:
Some 42S operations result in a complex number and there is no way the converter can know this for all possible cases. If you suspect this might happen, or are getting errors where matrices or complex numbers are trying to be stored in numeric registers (which is not supported on the 42S) then use the rpn: named directive on that line of Python code, so that the resulting complex number or matrix is guaranteed to be assigned to a named variable instead of the default, which is a numbered register.
When placed in a comment next to a function definition, will auto convert all parameters into integers, at runtime.
def f(a, b): # rpn: int
pass
Every function you define with def (except for the first one) will be a local function with a local label. By adding # rpn: export next to the def, it will be converted into a global string label. E.g. without the directive
def main():
pass
def useful():
pass
main()
useful()
Will generate
LBL "main"
RTN
LBL A
RTN
XEQ "main"
XEQ A
Notice the function ‘useful’ is converted into a local label. This is usually great, because you are not polluting the calculator with lots of global labels every time you define a useful subroutine. Only the first def automatically gets a global name (and you can call that first def anything you want, not just ‘main’).
Now let’s see what happens when we add the rpn directive.
def main():
pass
def useful(): # rpn: export
pass
LBL "main"
RTN
LBL "useful"
RTN
XEQ "main"
XEQ "useful"
Notice that both functions now have global string labels, so that you can execute them using XEQ. The word ‘export’ was used because that is what the HP Prime calculator uses.
Here is the code for the first screenshot above:
def dd1_draw_demo1():
FIX(0)
CLLCD()
draw_line(0, 0, 10, 10)
draw_line(10, 10, 20, 5)
draw_line(20, 5, 100, 16)
draw_line(100, 16, 131, 1)
draw_rect(70, 4, 5, 5)
fill_rect(18, 9, 12, 4)
draw_circle(96, 8, 5)
fill_circle(125, 10, 3)
The draw_line and draw_circle etc. routines are written in Python and converted to RPN too. They can be found in the Examples section of the website. https://pyrpn.herokuapp.com/examples?tag=Drawing
The graphic drawing functions are:
The DM42 has a larger screen which can be taken advantage of:
Thanks to Olivier Lécluse for testing and providing these screen shots.
You may have noticed that some extra RPN support functions are sometimes generated by the Python to RPN converter.
These functions have nothing directly to do with your Python code but are part of what the converter needs at ‘runtime’ to support the RPN code running. For example you might see the following towards the end of the RPN generated:
...
RTN
LBL 70
"-Utility Funcs-"
RTN
LBL 81
...
Just ignore this extra code. The more functionality you use (e.g. lists and dictionaries, booleans etc), then more support code is needed. If this worries you, then look at the next section re non embedded rpn support functions mode.
This is an advanced alternative. Rather than generating “embedded” RPN support code, you can instead keep your programs much shorter, and reference a global support library of utility functions.
This is the Python RPN Utility Functions program.
It contains all the required subroutines for running programs generated by the Python to RPN converter.
Normally you do not need this program because any required utility subroutines are automatically embedded into your RPN. However you can save memory by keeping your generated RPN small, and having it call the subroutines in this program instead. To do so, uncheck the Auto include checkbox on the main Converter page.
Warning: New subroutines are added regularly, so make sure you delete this "PyLIB" program and paste the latest version into your calculator/Free42 regularly.
The main way you will be running all this code is in Free42.
Example Python program:
Python:
def looper(n):
# Counts from 0..n adding up the numbers into a total
total = 0
for i in range(n+1):
display(i, total)
total += i
return total
def display(val, total):
VIEW(val)
PSE()
VIEW(total)
Note the use of a local subroutine, passing parameters, use of expressions and use of a for loop, as well as calling RPN built in functions like VIEW and PSE.
Converted to RPN:
00 LBL "looper"
01 STO 00
02 RDN
03 0
04 STO 01
05 0
06 RCL 00
07 1
08 +
09 XEQ d
10 STO 02
11 LBL 00
12 ISG 02
13 GTO 01
14 GTO 02
15 LBL 01
16 RCL 02
17 IP
18 RCL 01
19 XEQ A
20 RCL 02
21 IP
22 STO+ 01
23 GTO 00
24 LBL 02
25 RCL 01
26 RTN
27 LBL A
28 STO 00
29 RDN
30 STO 01
31 RDN
32 VIEW 00
33 PSE
34 VIEW 01
35 RTN
36 RTN
37 LBL d
38 CF 99
39 1
40 -
41 X<>Y
42 1
43 -
44 X>=0?
45 GTO e
46 ABS
47 SF 99
48 LBL e
49 X<>Y
50 1000
51 /
52 +
53 FS?C 99
54 +/-
55 RTN
Steps:
Enter the Python code into the converer and press the convert button.
Copy the generated RPN code out of the converter - ensure it has no comments and has line numbers. Simply hit the “Copy for Free42” button to ensure this.
The RPN code is now ready to be pasted into the Free42 calculator app.
Run Free42.
Go into program mode by hitting SHIFT PRGM
Open a new program and hit GTO ..
Paste the RPN by hitting CMD-V (Mac) or CTRL-V (PC).
Hit ESC to get out of program mode.
Type a number e.g. 8 and XEQ “Looper” (you should just be able to select LOOPER from the menu that appears after you hit the XEQ button)
I like to have the printer output on at the same time to see a running output. Get to your 42S print settings via SHIFT PRINT ꜛ
If you make a mistake, you should delete the old program before pasting in a newer version of the same program. Otherwise both programs will exist and be duplicated, leading to confusion. Delete the old program by name by pressing SHIFT CLEAR CLP then selecting the old Looper program from the menu.
If you are a Python programmer, and into data science, you will probably know about NumPy. NumPy is the fundamental package for scientific computing with Python. It contains among other things a powerful N-dimensional array object. It is interesting to see how NumPy works and how we can do many of the same things on the HP42S with similar syntax.
Let’s work through an example and show the NumPy commands vs the Python to RPN converter commands to generate the same effect on the HP42S. We will create a matrix of 0’s and inject into it a matrix of 1’s.
I’m using PyCharm scientific view to visualise the NumPy matrices and the Free42 virtual printer to visualise the Free42 matrices.
Note this version will not run on your 42S, even with the converter. This is an example of how the big boys do it on their Desktop PC, using a real installation of Python and of the NumPy library. Once you have Python istalled, install NumPy with ‘pip install numpy’ from the command line. Then run ‘python’ to get a python prompt and type along with this example.
import numpy as np
Let’s create a matrix filled with zeros, and a smaller matrix, filled with ones.
zeros = np.zeros((10,11))
ones = np.ones((2,3))
The zeros matrix
The ones matrix
Now let’s change a few values in the centre of the ones matrix
ones[0,1] = 99
ones[1,1] = 98
The ones matrix - with a few changed cells - ‘99’ and ‘98’
Finally, let’s inject the one’s matrix into the zero’s matrix at coodinates 2,5 (note this is 0 based counting).
zeros[2:4, 5:8] = ones
This is equivalent to a HP42S PUTM command. With the NumPy command we had to specify the complete row range 2:4 to be replaced using from:to syntax, which is called ‘slice’ syntax. And the complete column range too, which is 5:8. More specifically:
The zeros matrix - with the injected ones matrix.
Let’s do the same as the above on the HP42S using Python to RPN syntax and calls to HP42S commands.
Let’s create a matrix filled with zeros and a smaller matrix, filled with ones.
zeros = NEWMAT(10,11)
ones = NEWMAT(2,3)
ones += 1
HP42S matrices are initialised to 0 so we add 1 to all elements of the ones matrix. Lets look at the results
The zeros matrix on the left (truncated for brevity) and the ones matrix on the right (complete)
Changing values in the ones matrix is identical syntax to NumPy
ones[0,1] = 99
ones[1,1] = 98
The ones matrix - with a few changed cells - ‘99’ and ‘98’
Finally, let’s inject the one’s matrix into the zero’s matrix at coodinates 2,5 (note this is 0 based counting). Again, this syntax is the same as NumPy. 👍
zeros[2:4, 5:8] = ones
The zeros matrix - with the injected ones matrix.
As a bonus, the Python to RPN converter relaxes the NumPy syntax just a little so you can also use
zeros[2:, 5:] = ones
instead of
zeros[2:4, 5:8] = ones
in other words, you can leave off the ‘to’ part of the range, since that is implicitly derived from the size of the matrix you are injecting. I hope the NumPy gods will forgive this transgression of syntax - though after all, the HP42S command PUTM is similarly relaxed and just needs the target coordinates specified.
Note I am using occassional call to PRX to print the matrix to the Free42 virtual printer. Delete or comment these lines out with the # symbol if you don’t want them, or if you have a real 82240 printer and want to save paper! Then again, the matrices here are not that big...
def numpy1():
zeros = NEWMAT(10,11)
ones = NEWMAT(2,3)
ones += 1
PRX(zeros)
PRX(ones)
ones[0,1] = 99
ones[1,1] = 98
PRX(ones)
zeros[2:4, 5:8] = ones
PRX(zeros)
# Visualize as pixels :-)
CLLCD()
for row in range(0,10):
for col in range(0,11):
if zeros[row, col] == 0:
PIXEL(row, col)
:-)
Note: the shape of the matrix as graphic is slightly high because it seems that HP42S pixels are rectangular not square!
Just in case this is obvious to me but not to users
Python (or any language) comprises of syntax which gives users capabilities like variable storage, expressions, operators, looping, if - else decision making, custom functions with parameters and data structures. The HP RPN language also has such capabilities, although it has different syntax. For example in Python you can define a for loop with the for i in range() construct, whereas in RPN you define a loop via LBL nn ... ISG nn GTO nn etc. See Rosetta Code for more examples. My tool 'translates' Python code syntax into RPN code syntax.
In addition to the language itself, there is the library ecosystem of pre-built functions available to a language. Python has many libraries of functions that come with it plus many more you can install. RPN on the 42S also has a library of many mathematical and statistical functions, built into the calculator.
With my code translator tool, you must always remember that in the end, the generated code will be running on the 42S or Free42. In order to specify a call to a built in 42S function using Python syntax, you simply specify the actual name of that 42S function in uppercase in Python, just as you would in RPN - passing in any parameters that it need in brackets. Thus if you want to call VIEW 00 then in Python you call VIEW(a) or VIEW(b) where 'a' and 'b' are the variables that you want to view.
Python doesn't know about a stack - it only cares about variables. So you cannot and should not refer to stack X, Y etc. in Python. Thus VIEW(ST X) is not allowed to be specified in Python, plus its not even legal syntax because of the space between ST and X - though some variation e.g. like VIEW(STX) might be legal syntax but as I say, not allowed.
Deciding how to handle uppercase RPN commands specified in Python that refer the stack always require some thought - I typically allow them to be specified in Python but only allow them to refer to variables (which are mapped to RPN registers during the conversion) and not to the stack or to a numeric register. The STO and RCL command are not supported at all because they result in something moving from or to the stack. Same with RDN, X<>Y etc. All stack based operations are meaningless in Python.
Interestingly, the generated RPN does actually use the stack extensively but only as a workspace for calculations and for passing parameters to functions and receiving return values.
And you shouldn’t worry about or try to refer to 42S registers in Python either, so whilst VIEW(total) is ok, VIEW(01) is not allowed. In Python there are only variables. Yes each Python variable happens to get mapped to a 42S register during the translation, but that mapping is to a numbered register, is entirely arbitrary and not something you need to worry about. No offence to lovers of RPN, but for this project, RPN is targeted and treated as mere machine code or byte code - not something a high level language Python programmer cares much about. If you really want a Python variable to map to a specific 42S register, then use an uppercase Python variable name, which will be mapped to the same uppercase RPN named register name.
2021: Update: Whilst register use is generally not supported (use Python variables instead) sometimes it is useful to access a specific RPN calculator register e.g. a statistical register like 11 or 12 etc. You can do this with e.g. 'RCL(12)' and this instruction will be inserted into the converted RPN program. Typically entering the Python code 'x = RCL(12)' is more useful since you can then use that value somewhere or even display it e.g. print(x)
Just in case it isn't clear, any Python code that references the myriad HP42S functions will not run in Python on a Mac or PC. Well, it will technically run for a while, until it hits a 42S functions like SIN or PRV etc. Then the Python program will stop with an error. This is because those library functions do not exist on the desktop. Yes, someone could write a library of all the 42S functions and emulate the display output functionality so that VIEW etc would also work on a Mac or PC. This would actually be possible if say, the Free42 C libraries were wrapped and exposed to desktop Python. However my translator does not attempt to go down that road. It is content with users being able to write code in Python, calling HP42S functions here and there - and having that all convert to RPN code that actually does run on a HP42S or Free42.
The reason for the tool is that some people might find Python easier and more readable than RPN. Python has powerful structured programming concepts which replaced GOTO statements many decades ago. There are many other benefits to a high level language like Python - which I won't go into. What is fascinating is that we can now program a HP42S in Python. This potentially extends the life of the classic HP42S/Free42 calculator, which in my opinion has a design that is elegant, cohesive and perhaps even beautiful. Later calculators might have had more functions or more labels on their keys but they don't have the zen of the 42S. Even the 41c was a dry run, paving the way to the 42S, in my opinion.
For a list of HP42S Commands and how to call each RPN command using Python to Rpn.
https://pyrpn.herokuapp.com/cmds
The following Python commands are unsupported. Call a RPN command instead.
‘abs',
'dict',
'help',
'min',
'setattr',
'all',
'dir',
'hex',
'next',
'slice',
'any',
'divmod',
'id',
'object',
'sorted',
'ascii',
'enumerate',
'input',
'raw_input',
'oct',
'staticmethod',
'bin',
'eval',
'int',
'open',
'str',
'bool',
'exec',
'isinstance',
'ord',
'sum',
'bytearray',
'filter',
'issubclass',
'pow',
'super',
'bytes',
'float',
'iter',
# 'print',
'tuple',
'callable',
'format',
# 'len',
'property',
'type',
'chr',
'frozenset',
'list',
# 'range',
'vars',
'classmethod',
'getattr',
'locals',
'repr',
'zip',
'compile',
'globals',
'map',
'reversed',
'__import__',
'complex',
'hasattr',
'max',
'round',
'delattr',
'hash',
'memoryview',
'set',
# Extras for lists
'cmp',
Registers 00-NN - allocated as variables as needed when lowercase variables are encountered
Uppercase Named Registers e.g. "FRED" - created when python code has uppercase variable names
Lowercase Named Registers - not used unless # rpn: named added to Python code as a comment.
Named register pSaveT - used by pISG
Named register pISGvar - used by p2MxIJ
Named registers pX, Py, PZ, pT - used to store stack
Labels ABCDEFGHIJabcde - allocated for user python functions as needed
Rpn Support Function library labels and flags as follows:
LOCAL_LABEL_FOR_PyLIB = 50
# skip labels are resuable, just ensure they will be found in a forward search
SKIP_LABEL1 = 51
SKIP_LABEL2 = 52
SKIP_LABEL3 = 53
# unique cos needs to go backwards in jump
LOCAL_LABEL_FOR_LIST_BACK_JUMP = 54
LOCAL_LABEL_FOR_LIST_BACK_JUMP2 = 55
LOCAL_LABEL_FOR_2D_MATRIX_FIND = 56
# Could be allocated but just easier to set these because they are within a larger code chunk
LIST_PLUS = 57
LIST_MINUS = 58
LIST_CLIST = 59
# all remaining p* functions get allocated from here, via _create_local_labels()
LOCAL_LABEL_START_FOR_Py = 60 # .. 99
# Flags
FLAG_PYTHON_RPNLIB_GENERAL_PURPOSE = '00'
FLAG_LIST_1D_2D_MODE = '01'
FLAG_LIST_AUTO_CREATE_IF_KEY_NOT_FOUND = '02'
FLAG_PYTHON_USE_1 = 99
FLAG_PYTHON_USE_2 = 98
Tip: you don’t need to care about labels, flags or registers
When programming in Python, you don’t need to care about labels, flags or registers - so the fact that the converter is “hogging” all these resources should be of no concern to you! Plus all the local labels are local to a program, so won’t affect any other program on your calculator.
Maybe you don’t like the output?
The output of this converter may not be to your taste. It's certainly not “optimised” like hand crafted RPN program is. RPN is treated as generated ‘machine code’ - so it's going to be longer and a bit unclever - sorry!
There will be a phase of this project that attempts to optimise generated code. Its just not a priority yet.
If you are not calling any HP42S functions, then yes. If you are calling RPN functions then to test this same program in a Desktop Python interpreter, potentially mock out the RPN function calls:
def VIEW(a): print(a)
def PSE(): pass
And type looper(4) if you are in the Python interactive interpreter, or python looper.py if you have typed the Python into a python module.
Output of the Python version:
Some ideas
Python is easy to learn and use, and has a massive following in the scientific and data science communities. To learn Python, see these resources.
I’d love to have your feedback, bug reports and help
If you want something specifically supported - please email Andy.
Please also consider joining the issue tracker where requests are discussed and prioritised. I’d love to get more brain power from lovers of the HP42S.
Its free - except for a small Google ad at the bottom of some converter webpages
I'm experimenting with its placement and whether to keep it there at all. I've spent several months on this project with many late nights and all my Xmas holidays. There is still potentially a ton of work ahead - depending on how popular this project is.
So I'm hoping at least I'll get some ad clicks - and one day be able to treat myself to a reward of a DM42 😄
Latest news and functionality
HP42S simulator - available on all platforms Free42
Rebirth of the HP42S as hardware DM42 and discussed here. User manual here.
Swiss Micros HP42S online raw file converter
Overviews of the 42S
hp42s.com site dedicated to the 42S - via wayback machine
An Alternative HP-42S/Free42 Manual
Google search for HP42S
Numworks python based hardware calculator.
Python is now arguably the most popular programming language, and still growing rapidly - read The Incredible Growth of Python.
Python to RPN facebook page https://www.facebook.com/pythonrpn/
Google Plus HP calculator page https://plus.google.com/communities/113284589473614633679
Feedback, comments and feature suggestions - please email Andy Bulka. Consider also joining the issue tracker to log bugs and discuss design decisions etc.