Post Reply 
Python to FOCAL Compiler
10-05-2015, 10:58 PM
Post: #1
Python to FOCAL Compiler
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:

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.


Attached File(s)
.zip  python_to_focal_compiler.zip (Size: 3.58 KB / Downloads: 28)
Find all posts by this user
Quote this message in a reply
Post Reply 


Messages In This Thread
Python to FOCAL Compiler - Thomas Klemm - 10-05-2015 10:58 PM
RE: Python to FOCAL Compiler - Sukiari - 10-06-2015, 03:36 AM
Java to FOCAL Compiler - Thomas Klemm - 10-07-2015, 04:38 PM



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