Producing a Python module (The GNU Modula-2 Compiler)

### 2.16 How to produce a Python module ¶

This section describes how it is possible to produce a Python module from Modula-2 code. There are a number of advantages to this approach, it ensures your code reaches a wider audience, maybe it is easier to initialize your application in Python.

The example application here is a pedagogical two dimensional gravity next event simulation. The Python module needs to have a clear API which should be placed in a single definition module. Furthermore the API should only use fundamental pervasive data types and strings. Below the API is contained in the file `twoDsim.def`:

    DEFINITION MODULE twoDsim ;

    EXPORT UNQUALIFIED gravity, box, poly3, poly5, poly6, mass,
                       fix, circle, pivot, velocity, accel, fps,
                       replayRate, simulateFor ;
    (*
       gravity - turn on gravity at: g m^2
    *)

    PROCEDURE gravity (g: REAL) ;

    (*
       box - place a box in the world at (x0,y0),(x0+i,y0+j)
    *)

    PROCEDURE box (x0, y0, i, j: REAL) : CARDINAL ;

    (*
       poly3 - place a triangle in the world at:
               (x0,y0),(x1,y1),(x2,y2)
    *)

    PROCEDURE poly3 (x0, y0, x1, y1, x2, y2: REAL) : CARDINAL ;

    (*
       poly5 - place a pentagon in the world at:
               (x0,y0),(x1,y1),(x2,y2),(x3,y3),(x4,y4)
    *)

    PROCEDURE poly5 (x0, y0, x1, y1,
                     x2, y2, x3, y3, x4, y4: REAL) : CARDINAL ;

    (*
       poly6 - place a hexagon in the world at:
               (x0,y0),(x1,y1),(x2,y2),(x3,y3),(x4,y4),(x5,y5)
    *)

    PROCEDURE poly6 (x0, y0, x1, y1,
                     x2, y2, x3, y3,
                     x4, y4, x5, y5: REAL) : CARDINAL ;

    (*
       mass - specify the mass of an object and return the, id.
    *)

    PROCEDURE mass (id: CARDINAL; m: REAL) : CARDINAL ;

    (*
       fix - fix the object to the world.
    *)

    PROCEDURE fix (id: CARDINAL) : CARDINAL ;

    (*
       circle - adds a circle to the world.  Center
                defined by: x0, y0 radius, r.
    *)

    PROCEDURE circle (x0, y0, r: REAL) : CARDINAL ;

    (*
       velocity - give an object, id, a velocity, vx, vy.
    *)

    PROCEDURE velocity (id: CARDINAL; vx, vy: REAL) : CARDINAL ;

    (*
       accel - give an object, id, an acceleration, ax, ay.
    *)

    PROCEDURE accel (id: CARDINAL; ax, ay: REAL) : CARDINAL ;

    (*
       fps - set frames per second.
    *)

    PROCEDURE fps (f: REAL) ;

    (*
       replayRate - set frames per second during replay.
    *)

    PROCEDURE replayRate (f: REAL) ;

    (*
       simulateFor - render for, t, seconds.
    *)

    PROCEDURE simulateFor (t: REAL) ;

    END twoDsim.

The keyword `UNQUALIFIED` can be used to ensure that the compiler will provide externally accessible functions `gravity`, `box`, `poly3`, `poly5`, `poly6`, `mass`, `fix`, `circle`, `pivot`, `velocity`, `accel`, `fps`, `replayRate`, `simulateFor` rather than name mangled alternatives. Hence in our Python3 application we could write:

    #!/usr/bin/env python3

    from twoDsim import *

    b = box (0.0, 0.0, 1.0, 1.0)
    b = fix (b)
    c1 = circle (0.7, 0.7, 0.05)
    c1 = mass (c1, 0.01)
    c2 = circle (0.7, 0.1, 0.05)
    c2 = mass (c2, 0.01)
    c2 = fix (c2)
    gravity (-9.81)
    fps (24.0*4.0)
    replayRate (24.0)
    print ("creating frames")
    try:
        simulateFor (1.0)
        print ("all done")
    except:
        print ("exception raised")

which accesses the various functions defined and implemented by the module `twoDsim`. The Modula-2 source code is compiled via:

    $ gm2 -g -fiso -c -fswig twoDsim.mod
    $ gm2 -g -fiso -c -fmakelist twoDsim.mod
    $ gm2 -g -fiso -c -fmakeinit twoDsim.mod

The first command both compiles the source file creating `twoDsim.o` and produces a swig interface file `swig.i`. We now use `swig` and `g++` to produce and compile the interface wrappers:

    $ libtool --mode=compile g++ -g -c twoDsim_m2.cpp -o twoDsim_m2.lo
    $ swig -c++ -python3 twoDsim.i
    $ libtool --mode=compile g++ -c -fPIC twoDsim_wrap.cxx \
       -I/usr/include/python3 -o twoDsim_wrap.lo
    $ libtool --mode=compile gm2 -g -fPIC -fiso -c deviceGnuPic.mod
    $ libtool --mode=compile gm2 -g -fPIC -fiso -c roots.mod
    $ libtool --mode=compile gm2 -g -fPIC -fiso -c -fswig \
       twoDsim.mod -o twoDsim.lo

Finally the application is linked into a shared library:

    $ libtool --mode=link gcc -g twoDsim_m2.lo twoDsim_wrap.lo \
      roots.lo deviceGnuPic.lo \
       -L${prefix}/lib64 \
       -rpath `pwd` -lgm2 -lstdc++ -lm -o libtwoDsim.la
    cp .libs/libtwoDsim.so _twoDsim.so

The library name must start with `_` to comply with the Python3 module naming scheme.