GenApi.py

The language-agnostic nature of gen.py, while allowing itself to be clean and simple, transfers the complexity of the language-specific details to GenApi.py.

The original gmsh source code has a single GenApi.py file for all the target languages --- the arg objects have attributes for all the languages, and the API object has methods to write for each of them.

To see this, let's look at an easy example function, gmshModelSetCurrent, which sets the name of the current model --- this function has one input, a char * array, and no output. Here is how it is added to the API in gen.py:

doc = '''Set the current model to the model with name `name'. If several models have the same name, select the one that was added first.'''
model.add('setCurrent', doc, None, istring('name'))

From gmsh/api/gen.py

Here is what GenApi.py's write_c method writes in the C header file for this function:

/* Set the current model to the model with name `name'. If several models have
 * the same name, select the one that was added first. */
GMSH_API void gmshModelSetCurrent(const char * name,
                                  int * ierr);

From gmsh/api/gmshc.h

and here are the relevant snippets from GenApi.py that generate this code:

class arg:
    def __init__(self, name, value, python_value, julia_value, cpp_type,
                 c_type, out):
    ...
    # Note that self.c is generates the argument for the C function signature
    self.c = c_type + " " + name
    ...
...

def istring(name, value=None, python_value=None, julia_value=None):
    # Note that the arg is initialized with c_type = "const char *"
    a = arg(name, value, python_value, julia_value, "const std::string &",
            "const char *", False)
    a.python_arg = "c_char_p(" + name + ".encode())"
    ...
    a.texi_type = "string"
    return a

...
class API:
    ...
    def write_c(self):
        ...
        def write_module(module, c_namespace, cpp_namespace, indent):
            ...
            # Note that arg.c is used here to write the C function signature
            self.fwrite(f, 
              fnameapi + (",\n" + ' ' * len(fnameapi)).join(
                list((a.c for a in args + (oint("ierr"), )))) + ");\n")
        ...
    ...
...

Snippets from gmsh/api/GenApi.py

Let's start with the istring object. This is an object of the arg class. Note that in the initializer, the c_type passed is "const char *". The arg initializer in turn uses this to set its attribute c to c_type + " " + name. This is used in the write_c method of the API object to write the call signature in the C header file. This is how the C function signature is written to the header file.

This procedure is followed for all input and output types. For example, there is an object iint for an input int, ivectorsize for a vector of size_t's, etc. and similar for outputs, ostring for an output char * and so on. These objects have attributes that specify, among other things, the call signatures for various languages. For instance, if a function takes in an input vector of integers, then the C function needs two inputs, (int * list, size_t list_n), where list_n encodes the size of the list. The same goes for outputs.

We write our own GenApi.py that only writes the Morpho API, heavily adopting from the original. We will see how to do this in the next chapter.