Introduction to pytranslate
===========================

pytranslate package provides Maxima to Python translation functionality. The package is experimental, and the specifications of the functions in this package might change. It was written as a Google Summer of Code project by Lakshya A Agrawal (Undergraduate Student, IIIT-Delhi) in 2019. A detailed project report is available as a GitHub Gist (https://gist.github.com/LakshyAAAgrawal/33eee2d33c4788764087eef1fa67269e).

The package needs to be loaded in a Maxima instance for use, by executing load("pytranslate");

The statements are converted to python3 syntax. The file pytranslate.py must be imported for all translations to run, as shown in example.

Example:

     (%i1) load ("pytranslate")$

     /* Define an example function to calculate factorial */
     (%i2) pytranslate(my_factorial(x) := if (x = 1 or x = 0) then 1
                       else x * my_factorial(x - 1));
     (%o2)
     def my_factorial(x, v = v):
         v = Stack({}, v)
         v.ins({"x" : x})
         return((1 if ((v["x"] == 1) or (v["x"] == 0)) \
                   else (v["x"] * my_factorial((v["x"] + (-1))))))
     m["my_factorial"] = my_factorial

     (%i3) my_factorial(5);
     (%o3)                          120

     >>> from pytranslate import *
     >>> def my_factorial(x, v = v):
     ...     v = Stack({}, v)
     ...     v.ins({"x" : x})
     ...     return((1 if ((v["x"] == 1) or (v["x"] == 0)) \
     ...     else (v["x"] * my_factorial((v["x"] + (-1))))))
     ...
     >>> my_factorial(5)
     120

The Maxima to Python Translator works in two stages:

1. Conversion of the internal Maxima representation to a defined Intermediate Representation, henceforth referred as IR(mapping is present in share/pytranslate/maxima-to-ir.html)

2. The conversion of IR to Python.

Supported Maxima forms:

1. Numbers(including complex numbers)

2. Assignment operators

3. Arithmetic operators(+, -, *, ^, /, !)

4. Logical operators(and, or, not)

5. Relational operators(>, <, >=, <=, !=, ==)

6. Lists

7. Arrays

8. block

9. Function and function calls

10. if-else converted to Python conditionals

11. for loops

12. lambda form

Tests for pytranslate
---------------------

The tests for pytranslate are present at share/pytranslate/rtest_pytranslate.mac and can be run by executing batch(rtest_pytranslate, test);

Functions in pytranslate
========================

 -- Function: pytranslate (EXPR, [print-ir])
     Translates the expression EXPR to equivalent python3 statements. Output is printed in the stdout.

     Example:

          (%i1) load ("pytranslate")$

          (%i2) pytranslate('(for i:8 step -1 unless i<3 do (print(i))));
          (%o2)
          v["i"] = 8
          while not((v["i"] < 3)):
              m["print"](v["i"])
              v["i"] = (v["i"] + -1)
          del v["i"]

     EXPR is evaluated, and the return value is used for translation. Hence, for statements like assignment, it might be useful to quote the statement:

          (%i1) load ("pytranslate")$

          (%i2) pytranslate(x:20);
          (%o2)
          20

          (%i3) pytranslate('(x:20));
          (%o3)
          v["x"] = 20

     Passing the optional parameter (PRINT-IR) to pytranslate as t, will print the internal IR representation of expr and return the translated python3 code.

          (%i1) load("pytranslate");
          (%o1) pytranslate

          (%i2) pytranslate('(plot3d(lambda([x, y], x^2+y^(-1)), [x, 1, 10],
                             [y, 1, 10])), t);
          (body
           (funcall (element-array "m" (string "plot3d"))
                    (lambda
                        ((symbol "x") (symbol "y")
                         (op-no-bracket
                          =
                          (symbol "v")
                          (funcall (symbol "stack") (dictionary) (symbol "v"))))
                      (op +
                          (funcall (element-array (symbol "m") (string "pow"))
                                   (symbol "x") (num 2 0))
                          (funcall (element-array (symbol "m") (string "pow"))
                                   (symbol "y") (unary-op - (num 1 0)))))
                    (struct-list (string "x") (num 1 0) (num 10 0))
                    (struct-list (string "y") (num 1 0) (num 10 0))))
          (%o2)
          m["plot3d"](lambda x, y, v = Stack({}, v): (m["pow"](x, 2) + m["\
          pow"](y, (-1))), ["x", 1, 10], ["y", 1, 10])

 -- Function: show_form (EXPR)
     Displays the internal maxima form of expr

          (%i4) show_form(a^b);
          ((mexpt) $a $b)
          (%o4) a^b

Extending pytranslate
=====================

Working of pytranslate:

  * The entry point for pytranslate is the function $pytranslate defined in share/pytranslate/pytranslate.lisp.

  * $pytranslate calls the function maxima-to-ir with the Maxima expression as an argument(henceforth referred as expr).

  * maxima-to-ir determines if expr is atomic or non-atomic(lisp cons form). If atomic, atom-to-ir is called with expr which returns the IR for the atomic expression.

     To define/modify translation for atomic expressions, make changes to the definition of atom-to-ir in accordance with the IR.

  * If expr is non-atomic, the function cons-to-ir is called with expr as an argument.

       * cons-to-ir looks for (caar expr) which specifies the type of expr, in hash-table *MAXIMA-DIRECT-IR-MAP* and if the type is found, then appends the retrieved IR with the result of lisp call (mapcar #'maxima-to-ir (cdr expr)), which applies maxima-to-ir function to all the elements present in the list. Effectively, recursively generate IR for all the elements present in expr and append them to the IR map for the type.

          Example:

               (%i9) show_form(a+b);
               ((MPLUS) $B $A)

               (%i10) pytranslate(a+b, t);
               (body (op + (element-array (symbol "v") (string "b")) \
               (element-array (symbol "v") (string "a"))))
               (%o10)
               (v["b"] + v["a"])

          Here, operator + with internal maxima representation, (mplus) is present in *MAXIMA-DIRECT-IR-MAP* and mapped to (op +) to which the result of generating IR for all other elements of the list (a b), i.e. (ELEMENT-ARRAY (SYMBOL "v") (STRING "b")) (ELEMENT-ARRAY (SYMBOL "v") (STRING "a")) is appended.

       * If (caar expr) is not found in *MAXIMA-DIRECT-IR-MAP*, then cons-to-ir looks for the type in *MAXIMA-SPECIAL-IR-MAP* which returns the function to handle the translation of the type of expr. cons-to-ir then calls the returned function with argument expr as an argument.

          Example:

               (%i11) show_form(g(x) := x^2);
               ((mdefine simp) (($g) $x) ((mexpt) $x 2))

               (%i12) pytranslate(g(x):=x^2, t);
               (body
                (body
                 (func-def (symbol "g")
                           ((symbol "x") (op-no-bracket = (symbol "v") (symbol "v")))
                           (body-indented
                               (op-no-bracket = (symbol "v") (funcall (symbol "stack") \
                               (dictionary) (symbol "v")))
                               (obj-funcall (symbol "v") (symbol "ins") (dictionary \
                               ((string "x") (symbol "x"))))
                               (funcall (symbol "return")
                                   (funcall (element-array (symbol "f") (string "pow"))
                                        (element-array (symbol "v") (string "x"))
                                                 (num 2 0)))))
                 (op-no-bracket = (element-array (symbol "f") (string "g")) \
                 (symbol "g"))))
               (%o12)
               def g(x, v = v):
                   v = Stack({}, v)
                   v.ins({"x" : x})
                   return(f["pow"](v["x"], 2))
               f["g"] = g

          Here, mdefine, which is the type of expr is present in *MAXIMA-SPECIAL-IR-MAP* which returns func-def-to-ir as handler function, which is then called with expr to generate the IR.

          To define/modify translation for a type, add an entry to *MAXIMA-DIRECT-IR-MAP* if only a part of the IR needs to be generated and the rest can be appended, otherwise, for complete handling of expr, add an entry to *MAXIMA-SPECIAL-IR-MAP* and define a function with the name defined in *MAXIMA-SPECIAL-IR-MAP* which returns the IR for the form. The function naming convention for ir generators is (type)-to-ir, where type is the (caar expr) for expression(mdefine -> func-def-to-ir). The function must return a valid IR for the specific type.

  * After the generation of IR, the function ir-to-python is called with the generated ir as an argument, which performs the codegen in a recursive manner.

       * ir-to-python looks for lisp (car ir) in the hash-table *IR-PYTHON-DIRECT-TEMPLATES*, which maps IR type to function handlers and calls the function returned with ir as an argument.

  * To extend the IR of pytranslate, define a function with the naming convention (type)-to-python and add the name to *IR-PYTHON-DIRECT-TEMPLATES*.