10-05-2015, 10:58 PM
Python Byte-Code
Ever since I noticed the similarity of the generated byte-code of a Python-program with a FOCAL program I wondered if this could be used to create programs for the HP-41C.
Let's start with an example that calculates the area of a circle with radius r:
This will compile to byte-code which then can be disassembled:
The goal is to generate the following FOCAL-program:
Of course this can't work in general due to the limitations of the HP-41C. But nonetheless the results so far are promising:
Celsius to Fahrenheit conversion
But can we deal with complex expressions involving mathematical functions?
Spherical Law of Cosines
Mach Number
Sometimes we have to rearrange the expression a little to avoid stack-overflow as with the famous formula for the mach number:
You may notice that the result isn't exactly how it is solved in the HP-67 manual as the expression 6.875E-6*25500 is simplified to 0.1753125.
Or then we can avoid stack-overflow by using local variables.
Quadratic Equation
In some cases the RDN command after each STO command could be removed but not in all. Thus the generated code isn't optimized but that can easily be done manually to shave off a byte here and there.
Fizz Buzz
The famous simple coding interview question:
A print statement is currently just mapped to the AVIEW command. But the last one should be VIEW X instead. I didn't come up with a simple solution for this but think this can easily be fixed manually. You may notice that all branches go to LBL 03 as a common exit-point. You can of course just use RTN instead.
Greatest Common Divisor
What about loops, you may wonder.
While the code is correct we can certainly remove LBL 02 as it isn't used. And then there's no need to swap a and b before storing them. Of course we're far away from the optimized solution below but it might be a good starting point.
Conditionals
How would you translate the following conditional?
Isn't it nice that you can let the compiler do the hard work?
Nested loop and break
This primitive program lists all prime factors of a given number:
Here again we have to replace AVIEW by VIEW X:
Mutual Inductunce of Coil Pair
This example stems from a recent thread:
We assume that we can use functions to calculate the complete elliptic integrals. For this we have to add a customized mapping:
The generated code has some similarities to the listing for the HP-67 in APPENDIX A:
Conclusions
There are some limitations:
It was fun to tinker a little with Python byte-code. I hope the result may be inspiring.
Kind regards
Thomas
To translate the examples in examples.py just run:
python compiler.py
The result is listed in examples.hp.
Ever since I noticed the similarity of the generated byte-code of a Python-program with a FOCAL program I wondered if this could be used to create programs for the HP-41C.
Let's start with an example that calculates the area of a circle with radius r:
Code:
def circle(r):
return pi * r ** 2
This will compile to byte-code which then can be disassembled:
Code:
2 0 LOAD_GLOBAL 0 (pi)
3 LOAD_FAST 0 (r)
6 LOAD_CONST 1 (2)
9 BINARY_POWER
10 BINARY_MULTIPLY
11 RETURN_VALUE
The goal is to generate the following FOCAL-program:
Code:
LBL "CIRCLE"
STO 00 ; r
RDN
PI
RCL 00 ; r
2
Y↑X
*
RTN
Of course this can't work in general due to the limitations of the HP-41C. But nonetheless the results so far are promising:
Celsius to Fahrenheit conversion
Code:
def fahrenheit(celsius):
return 9 * celsius / 5 + 32
Code:
LBL "FAHRENH"
STO 00 ; celsius
RDN
9
RCL 00 ; celsius
*
5
/
32
+
RTN
But can we deal with complex expressions involving mathematical functions?
Spherical Law of Cosines
Code:
def spherical_law_of_cosines(a, C, b):
return acos(cos(a)*cos(b)+sin(a)*sin(b)*cos(C))
Code:
LBL "SPHERIC"
STO 02 ; b
RDN
STO 01 ; C
RDN
STO 00 ; a
RDN
RCL 00 ; a
COS
RCL 02 ; b
COS
*
RCL 00 ; a
SIN
RCL 02 ; b
SIN
*
RCL 01 ; C
COS
*
+
ACOS
RTN
Mach Number
Sometimes we have to rearrange the expression a little to avoid stack-overflow as with the famous formula for the mach number:
Code:
def mach():
# original: Sqrt(5*(((((1+.2*(350/661.5)^2)^3.5-1)*(1-6.875E-6*25500)^-5.2656)+1)^.286-1))
return sqrt((((((.2*(350/661.5)**2+1)**3.5-1)*(1-6.875E-6*25500)**-5.2656)+1)**.286-1)*5)
Code:
LBL "MACH"
0.2
350
661.5
/
2
Y↑X
*
1
+
3.5
Y↑X
1
-
1
0.1753125
-
-5.2656
Y↑X
*
1
+
0.286
Y↑X
1
-
5
*
SQRT
RTN
You may notice that the result isn't exactly how it is solved in the HP-67 manual as the expression 6.875E-6*25500 is simplified to 0.1753125.
Or then we can avoid stack-overflow by using local variables.
Quadratic Equation
Code:
def qe(a, b, c):
p = b / a / -2
q = c / a
D = sqrt(p**2 - q)
return p + D, p - D
Code:
LBL "QE"
STO 02 ; c
RDN
STO 01 ; b
RDN
STO 00 ; a
RDN
RCL 01 ; b
RCL 00 ; a
/
-2
/
STO 03 ; p
RDN
RCL 02 ; c
RCL 00 ; a
/
STO 04 ; q
RDN
RCL 03 ; p
2
Y↑X
RCL 04 ; q
-
SQRT
STO 05 ; D
RDN
RCL 03 ; p
RCL 05 ; D
+
RCL 03 ; p
RCL 05 ; D
-
RTN
In some cases the RDN command after each STO command could be removed but not in all. Thus the generated code isn't optimized but that can easily be done manually to shave off a byte here and there.
Fizz Buzz
The famous simple coding interview question:
Code:
def fizbuz(n):
if n % 15 == 0:
print 'FIZZBUZZ'
elif n % 3 == 0:
print 'FIZZ'
elif n % 5 == 0:
print 'BUZZ'
else:
print n
Code:
LBL "FIZBUZ"
STO 00 ; n
RDN
RCL 00 ; n
15
MOD
0
X#Y?
GTO 00
"FIZZBUZZ"
AVIEW
GTO 03
LBL 00
RCL 00 ; n
3
MOD
0
X#Y?
GTO 01
"FIZZ"
AVIEW
GTO 03
LBL 01
RCL 00 ; n
5
MOD
0
X#Y?
GTO 02
"BUZZ"
AVIEW
GTO 03
LBL 02
RCL 00 ; n
AVIEW
LBL 03
RTN
A print statement is currently just mapped to the AVIEW command. But the last one should be VIEW X instead. I didn't come up with a simple solution for this but think this can easily be fixed manually. You may notice that all branches go to LBL 03 as a common exit-point. You can of course just use RTN instead.
Greatest Common Divisor
What about loops, you may wonder.
Code:
def gcd(a, b):
while b != 0:
a, b = b, a % b
return a
Code:
LBL "GCD"
STO 01 ; b
RDN
STO 00 ; a
RDN
LBL 00
RCL 01 ; b
0
X=Y?
GTO 01
RCL 01 ; b
RCL 00 ; a
RCL 01 ; b
MOD
X<>Y
STO 00 ; a
RDN
STO 01 ; b
RDN
GTO 00
LBL 01
LBL 02
RCL 00 ; a
RTN
While the code is correct we can certainly remove LBL 02 as it isn't used. And then there's no need to swap a and b before storing them. Of course we're far away from the optimized solution below but it might be a good starting point.
Code:
LBL "GCD"
LBL 00
X=0?
GTO 01
STO Z
MOD
GTO 00
LBL 01
RDN
END
Conditionals
How would you translate the following conditional?
Code:
def conditional(n):
if 0 <= n and n < 13 or n % 3 == 0:
print 'FOUND'
else:
print 'NOT FOUND'
Isn't it nice that you can let the compiler do the hard work?
Code:
LBL "CONDITI"
STO 00 ; n
RDN
0
RCL 00 ; n
X<Y?
GTO 00
RCL 00 ; n
13
X>Y?
GTO 01
LBL 00
RCL 00 ; n
3
MOD
0
X#Y?
GTO 02
LBL 01
"FOUND"
AVIEW
GTO 03
LBL 02
"NOT FOUND"
AVIEW
LBL 03
RTN
Nested loop and break
This primitive program lists all prime factors of a given number:
Code:
def factor(n):
p = 2
while n > 1:
if p**2 > n:
print n
break
while n % p == 0:
print p
n /= p
p += 1
Here again we have to replace AVIEW by VIEW X:
Code:
LBL "FACTOR"
STO 00 ; n
RDN
2
STO 01 ; p
RDN
LBL 00
RCL 00 ; n
1
X>=Y?
GTO 05
RCL 01 ; p
2
Y↑X
RCL 00 ; n
X>=Y?
GTO 01
RCL 00 ; n
AVIEW
GTO 06 ; break
GTO 01
LBL 01
LBL 02
RCL 00 ; n
RCL 01 ; p
MOD
0
X#Y?
GTO 03
RCL 01 ; p
AVIEW
RCL 00 ; n
RCL 01 ; p
/
STO 00 ; n
RDN
GTO 02
LBL 03
LBL 04
RCL 01 ; p
1
+
STO 01 ; p
RDN
GTO 00
LBL 05
LBL 06
RTN
Mutual Inductunce of Coil Pair
This example stems from a recent thread:
Code:
def mutind(r, R, x):
m = 4*r*R/((R+r)**2+x**2)
k = sqrt(m)
K = EllipticF(m)
E = EllipticE(m)
return ((1-m/2)*K-E)*8*pi*1e-7*sqrt(r*R)/k
We assume that we can use functions to calculate the complete elliptic integrals. For this we have to add a customized mapping:
Code:
function = {
(...)
# custom
'EllipticE' : 'ELLIPE',
'EllipticF' : 'ELLIPF',
}
The generated code has some similarities to the listing for the HP-67 in APPENDIX A:
Code:
LBL "MUTIND"
STO 02 ; x
RDN
STO 01 ; R
RDN
STO 00 ; r
RDN
4
RCL 00 ; r
*
RCL 01 ; R
*
RCL 01 ; R
RCL 00 ; r
+
2
Y↑X
RCL 02 ; x
2
Y↑X
+
/
STO 03 ; m
RDN
RCL 03 ; m
SQRT
STO 04 ; k
RDN
RCL 03 ; m
ELLIPF
STO 05 ; K
RDN
RCL 03 ; m
ELLIPE
STO 06 ; E
RDN
1
RCL 03 ; m
2
/
-
RCL 05 ; K
*
RCL 06 ; E
-
8
*
PI
*
1e-07
*
RCL 00 ; r
RCL 01 ; R
*
SQRT
*
RCL 04 ; k
/
RTN
Conclusions
There are some limitations:
- Runs only with Python 2.7.
- For-loop isn't supported.
- All functions use the same registers counting from 00.
- Recursion doesn't work.
- Probably a lot of things I'm not aware of.
It was fun to tinker a little with Python byte-code. I hope the result may be inspiring.
Kind regards
Thomas
Code:
Archive: python_to_focal_compiler.zip
Length Date Time Name
--------- ---------- ----- ----
5744 10-06-2015 00:01 compiler.py
1482 10-05-2015 23:40 examples.py
1951 10-05-2015 23:41 examples.hp
--------- -------
9177 3 files
To translate the examples in examples.py just run:
python compiler.py
The result is listed in examples.hp.