calligra

C language metaprogramming and code generation from Python


Keywords
code-generation, code-generator, metaprogramming, serialization
License
MIT
Install
pip install calligra==0.2

Documentation

calligra

Author: Elie ROUDNINSKI

calligra is a pure Python package that tries to modelize a subset of the C langage syntax in order to reason about C types, from Python scripts. Its main goals are to do metaprogrammation and code generation. It does not parse C code itself at all.

calligra was first designed to (un)serialize complex (but not too complex ...) C structures from JSON.

1   Installation

1.1   Requirements

calligra requires Python 3. It has been tested on Python 3.6 on Linux but it should work on 3.4 and 3.6 and on Windows also. At the moment, it does not require any external Python modules/packages, but this might change in the future.

As calligra is intended to generate code, some external dependencies might be needed to compile the generated code.

1.2   From github

You can clone this repository and install it with setuptools directly:

$ python3 setup.py install --user

1.3   From pip

As every pip available package, you can install it easily with the pip package:

$ python3 -m pip install --user calligra

1.4   Tests

Tests are available in the source distribution (either from github or from pip) and are located in the tests/ directory. You can run them with setuptools:

$ python3 setup.py test

2   Howto

2.1   Introduction

As specified before, calligra is intended to reason about C types at a Python level.

Currently, you can modelize the following types:

  • primary types like char, int, float, double etc.
  • C strings (char*)
  • enum
  • struct, named and anonymous
  • union, named and anonymous

and the following declaration modifiers:

  • pointers
  • array

Nested array of pointers or pointers to array are not supported.

At the moment, you have two choices:

  • define everything from Python
  • parse C code with the cparser importer module

In the future, you will be able to import definitions from:

  • JSON Schema

2.2   Examples

Lets start with a basic example. In the following code snippets we will be defining a C structure called person with 2 members:

  • a string name
  • an unsigned integer age

And then we will generate the associated C code.

First import the main modules:

import calligra
import calligra.stdlib

calligra module is where the C type/declaration syntax is modelized. calligra.stdlib is where standard C types are defined.

Then define the structure:

namespace = calligra.stdlib.namespace
person = calligra.struct(namespace, 'person')
person.add(
    calligra.declaration(
        namespace, namespace.get('char'), 'name', pointer = True
    )
)
person.add(
    calligra.declaration(
        namespace, namespace.get('uint8_t'), 'age'
    )
)

Finally, generate the C code:

print(person.define())

This should generate something similar to:

struct person {
    char *name;
    uint8_t age;
};

More advanced examples are located in the examples/ directory.

3   Modules

3.1   Conversion

Conversion modules are located in the calligra/convert/ directory and are meant to (un)serialize C types to and from another format (like JSON).

Currently available conversion modules are:

  • calligra.convert.jansson: to convert C types to and from JSON using the Jansson library.

3.1.1   Jansson

In order to use the jansson conversion module, just import the calligra.convert.jansson module:

import calligra.convert.jansson

After that, every type should now have a to_json and a from_json method. Those are actually calligra.functions object which you can define to generate the corresponding C code:

print(person.to_json.define())

Which should generate something similar to:

json_t *person_to_json(struct person const *person);

And for the function body:

print(person.to_json.code(body = True))

Which should generate something similar to (non-contractual code):

json_t *person_to_json(struct person const *person) {
    json_t *json = json_object(), *child;
    if(!json) {
        return NULL;
    }
    /*name*/
    if((person != NULL) && ((*person).name != NULL) && (*(*person).name != 0)) {
        child = json_string((*person).name);
        if(!child || json_object_set_new_nocheck(json, "name", child) != 0) {
            if(child) {
                json_decref(child);
            }
            json_decref(json);
            return NULL;
        }
    }
    /*age*/
    if(person != NULL) {
        child = json_integer((*person).age);
        if(!child || json_object_set_new_nocheck(json, "age", child) != 0) {
            if(child) {
                json_decref(child);
            }
            json_decref(json);
            return NULL;
        }
    }
    return json;
}

3.2   Importer

Importer modules are located in the calligra/importer/ directory and are meant to import C types from another format (like C).

Currently available importer modules are:

  • calligra.importer.cparser: to import C types directly from C code using the pycparser package.