Library for using generic and type safe container in pure C language (C99 or C11) for a wide collection of container (comparable to the C++ STL).
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
bench Happy new year! Jan 2, 2019
doc Update Jan 15, 2019
example Change deprecated functions. Oct 29, 2018
tests Variable number of threads for concurrent memory pool Jan 2, 2019
.gitignore Ignore a*.dat file too. Apr 20, 2018
LICENSE Happy new year! Jan 2, 2019
Makefile Fix missing header in install Dec 14, 2018
README.md Add operator GET_SIZE Nov 25, 2018
m-algo.h Happy new year! Jan 2, 2019
m-array.h Happy new year! Jan 2, 2019
m-atomic.h Workaround issue of stdatomic on MSYS2/GCC Jan 4, 2019
m-bitset.h Happy new year! Jan 2, 2019
m-bptree.h Happy new year! Jan 2, 2019
m-buffer.h Use of new macro M_CACHELINE_ALIGN Jan 15, 2019
m-c-mempool.h Add missing return after M_MEMORY_FULL Jan 5, 2019
m-concurrent.h Happy new year! Jan 2, 2019
m-core.h Add M_CACHELINE_ALIGN macro Jan 15, 2019
m-deque.h Happy new year! Jan 2, 2019
m-dict.h Happy new year! Jan 2, 2019
m-genint.h Happy new year! Jan 2, 2019
m-i-list.h Happy new year! Jan 2, 2019
m-i-shared.h Happy new year! Jan 2, 2019
m-list.h Happy new year! Jan 2, 2019
m-mempool.h Happy new year! Jan 2, 2019
m-mutex.h Happy new year! Jan 2, 2019
m-prioqueue.h Happy new year! Jan 2, 2019
m-rbtree.h Happy new year! Jan 2, 2019
m-shared.h Use of new macro M_CACHELINE_ALIGN Jan 15, 2019
m-snapshot.h Use of new macro M_CACHELINE_ALIGN Jan 15, 2019
m-string.h Happy new year! Jan 2, 2019
m-tuple.h Happy new year! Jan 2, 2019
m-variant.h Happy new year! Jan 2, 2019
m-worker.h Happy new year! Jan 2, 2019

README.md

M*LIB: a Generic type-safe Container Library in C language

Overview

M*LIB (M star lib) is a library allowing the programmer to use generic and type safe container in pure C language, aka handling generic containers. The objects within the containers can have proper constructor, destructor (and other methods): this will be handled by the library. This allows to construct fully recursive objects (container-of[...]-container-of-type-T).

This is an equivalent of the C++ STL but for standard ISO C99. This is not a strict mapping and both the STL and M*LIB have their exclusive containers: See here for details.

M*LIB should be portable to any systems that support ISO C99 (some optional features need ISO C11 support).

M*LIB is only composed of a set of headers. There is no C file: you just have to put the header in the search path of your compiler, and it will work. There is no dependency (except some other headers of M*LIB and the LIBC).

One of M*LIB's design key is to ensure safety. This is done by multiple means:

  • in debug mode, defensive programming is extensively used: the contracts of the function are checked, ensuring that the data are not corrupted. For example, Buffer overflow are checked in this mode through bound checking or the intrinsic properties of a Red-Black tree are verified.
  • very few casts are used within the library. Still the library can be used with the greatest level of warnings by a C compiler without any aliasing warning.
  • the genericity is not done directly by macro, but indirectly by making them define inline functions with the proper prototypes: this allows the user calls to have proper warning checks.

Other key designs are:

  • do not rewrite the C library and just wrap around it (for example don't rewrite sort but stable sort),
  • do not make users pay the cost of what they don't need.

Due to the nature of the C language, 'type safe' means that a proper warning shall be generated by the compiler in case of wrong type passed as arguments to the functions (and not errors). You can also request the compiler to transform all warnings into errors to get proper errors too if you want.

M*LIB should still be quite-efficient: even if the implementation may not always be state of the art, there is no overhead in using this library rather than using direct C low-level access: the compiler is able to fully optimize the library calls since all the functions are declared as inline. In fact, M*LIB is one of the fastest generic C library you can find.

M*LIB uses internally the 'malloc', 'realloc' and 'free' functions to handle the memory pool. This behavior can be overridden at different level. M*LIB default policy is to abort the program if there is a memory error. However, this behavior can also be customized.

M*LIB may use a lot of assertions in its implementation to ensure safety: it is highly recommended to properly define NDEBUG for released programs.

M*LIB is distributed under BSD-2 simplified license.

It is strongly advised not to read the source to know how to use the library but rather read the examples or the tests.

Components

The available containers that doesn't require the user structure to be modified are:

  • m-array.h: header for creating array of generic type and of variable size,
  • m-list.h: header for creating singly-linked list of generic type,
  • m-deque.h: header for creating double-ended queue of generic type and of variable size,
  • m-dict.h: header for creating generic dictionary or set of generic types,
  • m-tuple.h: header for creating arbitrary tuple of generic type,
  • m-rbtree.h: header for creating binary sorted tree,
  • m-bptree.h: header for creating B+TREE of generic type,
  • m-variant.h: header for creating arbitrary variant of generic type,
  • m-prioqueue.h: header for creating priority queue of generic type and of variable size,

The available containers of M*LIB for thread synchronization are:

  • m-buffer.h: header for creating fixed-size queue (or stack) of generic type (multiple produce / multiple consumer),
  • m-snapshot: header for creating 'snapshot' buffer for sharing synchronously data (thread safe).
  • m-shared.h: header for creating shared pointer of generic type.
  • m-concurrent.h: header for transforming a container into a concurrent container.

The following containers are intrusive (You need to modify your structure to add fields needed by the container):

  • m-i-list.h: header for creating doubly-linked intrusive list of generic type,
  • m-i-shared.h: header for creating intrusive shared pointer of generic type (Thread Safe),

Other headers offering other functionality are:

  • m-bitset.h: header for creating bit set (or "packed array of bool"),
  • m-string.h: header for creating dynamic variable-length string,
  • m-algo.h: header for providing various generic algorithms to the previous containers.
  • m-mempool.h: header for creating specialized & fast memory allocator.
  • m-worker.h: header for providing an easy pool of workers to handle work orders, used for parallelism tasks.
  • m-core.h: header for meta-programming with the C preprocessor.

Finally headers for compatibility with non C11 compilers:

  • m-atomic.h: header for ensuring compatibility between C's stdatomic.h and C++'s atomic header. Provide also an implementation over mutex if none is available.
  • m-mutex.h: header for providing a very thin layer across multiple implementation of mutex/threads (C11/PTHREAD/WIN32).

Each containers define their iterators.

All containers try to expose the same interface: if the method name is the same, then it does the same thing and is used in the same way. In some rare case, the method has to be adapted to the container.

Each header can be used separately from others: dependency between headers have been kept to the minimum.

Dependence between headers

Build & Installation

M*LIB is only composed of a set of headers, as such there is no build for the library. The library doesn't depend on any other library than the libc.

To run the test suite, run:

   make check

To generate the documentation, run:

   make doc

To install the headers, run:

   make install PREFIX=/my/directory/to/install

How to use

To use these data structures, you include the desired header, instantiate the definition of the structure and its associated methods by using a macro _DEF for the needed type. Then you use the defined functions. Let's see a first simple example that creates a list of unsigned int:

#include <stdio.h>
#include "m-list.h"

LIST_DEF(list_uint, unsigned int)      /* Define struct list_uint_t and its methods */

int main(void) {
   list_uint_t list ;             /* list_uint_t has been define above */
   list_uint_init(list);          /* All type needs to be initialized */
   list_uint_push_back(list, 42); /* Push 42 in the list */
   list_uint_push_back(list, 17); /* Push 17 in the list */
   list_uint_it_t it;             /* Define an iterator to scan each one */
   for(list_uint_it(it, list)     /* Start iterator on first element */
       ; !list_uint_end_p(it)     /* Until the end is not reached */
       ; list_uint_next(it)) {    /* Set the iterator to the next element*/
          printf("%d\n",          /* Get a reference to the underlying */
            *list_uint_cref(it)); /* data and print it */
   }
   list_uint_clear(list);         /* Clear all the list */
}

[ Do not forget to add -std=c99 (or c11) to your compile command to request a C99 compatible build ]

This looks like a typical C program except the line with 'LIST_DEF' that doesn't have any semi-colon at the end. And in fact, except this line, everything is typical C program and even macro free! The only macro is in fact LIST_DEF: this macro expands to the good type for the list of the defined type and to all the necessary functions needed to handle such type. It is heavily context dependent and can generate different code depending on it. You can use it as many times as needed to defined as many lists as you want. The first argument of the macro is the name to use, e.g. the prefix that shall be added to all generated functions and types. The second argument of the macro is the type to embed within the container. It can be any C type. The third argument of the macro is optional and is the oplist to use. See below for more information.

You could replace LIST_DEF by ARRAY_DEF to change the kind of container (an array instead of a linked list) without changing the code below: the generated interface of a list or of an array is very similar. Yet the performance remains the same as hand-written code for both the list variant and the array variant.

This is equivalent to this C++ program using the STL:

#include <iostream>
#include <list>

typedef std::list<unsigned int> list_uint_t;
typedef std::list<unsigned int>::iterator list_uint_it_t;

int main(void) {
   list_uint_t list ;             /* list_uint_t has been define above */
   list.push_back(42);            /* Push 42 in the list */
   list.push_back(17);            /* Push 17 in the list */
   for(list_uint_it_t it = list.begin()  /* Iterator is first element*/
       ; it != list.end()         /* Until the end is not reached */
       ; ++it) {                  /* Set the iterator to the next element*/
       std::cout << *it << '\n';  /* Print the underlying data */
   }
}

As you can see, this is rather equivalent with the following remarks:

  • M*LIB requires an explicit definition of the instance of the list,
  • M*LIB code is more verbose in the method name,
  • M*LIB needs explicit construction and destruction (as plain old C requests),
  • M*LIB doesn't return a value to the underlying data but a pointer to this value:
    • this was done for performance (it avoids copying all the data within the stack)
    • and for generality reasons (some structure may not allow copying data).

Note: list_uint_t is internally defined as an array of structure of size 1. This has the following advantages:

  • you effectively reserve the data whenever you declare a variable,
  • you pass automatically the variable per reference for function calls,
  • you can not copy the variable by an affectation (you have to use the API instead).

You can also condense the M*LIB code by using the M_EACH & M_LET macros if you are not afraid of using syntactic macros:

#include <stdio.h>
#include "m-list.h"

LIST_DEF(list_uint, unsigned int)   /* Define struct list_uint_t and its methods */

int main(void) {
   M_LET(list, LIST_OPLIST(uint)) { /* Define & init list as list_uint_t */
     list_uint_push_back(list, 42); /* Push 42 in the list */
     list_uint_push_back(list, 17); /* Push 17 in the list */
     for M_EACH(item, list, LIST_OPLIST(uint)) {
       printf("%d\n", *item);       /* Print the item */
     }
   }                                /* Clear of list will be done now */
}

Here is another example with a complete type (with proper initialization & clear function) by using the GMP library:

#include <stdio.h>
#include <gmp.h>
#include "m-array.h"

ARRAY_DEF(array_mpz, mpz_t, (INIT(mpz_init), INIT_SET(mpz_init_set), SET(mpz_set), CLEAR(mpz_clear)) )

int main(void) {
   array_mpz_t array ;             /* array_mpz_t has been define above */
   array_mpz_init(array);          /* All type needs to be initialized */
   mpz_t z;                        /* Define a mpz_t type */
   mpz_init(z);                    /* Initialize the z variable */
   mpz_set_ui (z, 42);
   array_mpz_push_back(array, z);  /* Push 42 in the array */
   mpz_set_ui (z, 17);
   array_mpz_push_back(array, z); /* Push 17 in the array */
   array_it_mpz_t it;              /* Define an iterator to scan each one */
   for(array_mpz_it(it, array)     /* Start iterator on first element */
       ; !array_mpz_end_p(it)      /* Until the end is not reached */
       ; array_mpz_next(it)) {     /* Set the iterator to the next element*/
          gmp_printf("%Zd\n",      /* Get a reference to the underlying */
            *array_mpz_cref(it));  /* data and print it */
   }
   mpz_clear(z);                   /* Clear the z variable */
   array_mpz_clear(array);         /* Clear all the array */
}

As the mpz_t type needs proper initialization, copy and destroy functions we need to tell to the container how to handle such a type. This is done by giving it the oplist associated to the type. An oplist is an associative array where the operators are associated to methods. In the example, we tell to the container to use the mpz_init function for the INIT operator of the type, the mpz_clear function for the CLEAR operator of the type, the mpz_set function for the SET operator of the type, the mpz_init_set function for the INIT_SET operator of the type. See oplist chapter for more information.

We can also write the same example shorter:

#include <stdio.h>
#include <gmp.h>
#include "m-array.h"

// Register the oplist of a mpz_t. It is a classic oplist.
#define M_OPL_mpz_t() M_CLASSIC_OPLIST(mpz)
// Define an instance of a array of mpz_t (both type and function)
ARRAY_DEF(array_mpz, mpz_t)
// Register the oplist of the created instance of array of mpz_t
#define M_OPL_array_mpz_t() ARRAY_OPLIST(array_mpz, M_OPL_mpz_t())

int main(void) {
  // Let's define 'array' as an 'array_mpz_t' & initialize it.
  M_LET(array, array_mpz_t)
    // Let's define 'z1' and 'z2' to be 'mpz_t' & initialize it
    M_LET (z1, z2, mpz_t) {
     mpz_set_ui (z1, 42);
     array_mpz_push_back(array, z1);  /* Push 42 in the array */
     mpz_set_ui (z2, 17);
     array_mpz_push_back(array, z2); /* Push 17 in the array */
     // Let's iterate over all items of the container
     for M_EACH(item, array, array_mpz_t) {
          gmp_printf("%Zd\n", *item);
     }
  } // All variables are cleared with the proper method beyond this point.
  return 0;
}

There are two ways a container can known what is the oplist of a type:

  • either the oplist is passed explicitly for each definition of container and for the LET & EACH macros,
  • or the oplist is registered globally by defining a new macro starting with the prefix M_OPL_ and finishing with the name of type (don't forget the parenthesis). The macros performing the definition of container and the LET & EACH will test if such macro is defined. If it is defined, it will be used. Otherwise default methods are used.

Here we can see that we register the mpz_t type into the container with the minimum information of its interface needed.

We can also see in this example so the container ARRAY provides also a macro to define the oplist of the array itself. This is true for all containers and this allows to define proper recursive container.

Other examples are available in the example folder.

Internal fields of the structure are subject to change even for small revision of the library.

The final goal of the library is to be able to write code like this in pure C while keeping type safety and compile time name resolution:

let(list, list_uint_t) {
  push(list, 42);
  push(list, 17);
  for each (item, list) {
    print(item, "\n");
  }
}

OPLIST

An OPLIST is a fundamental notion of M*LIB that hasn't be used in any other library. In order to know how to operate on a type, M*LIB needs additional information as the compiler doesn't know how to handle properly any type (contrary to C++). This is done by giving an operator list (or oplist in short) to any macro that needs to handle the type. As such, an oplist as only meaning within a macro expansion. Fundamentally, this is the exposed interface of a type with documented ooperators using an associative array implemented with the only C preprocessor where the operators are the predefined keys and the methods are the values.

An oplist is an associative array of operator over methods in the following format:

(OPERATOR1(method1), OPERATOR2(method2), ...)

The function 'method1' is used to handle 'OPERATOR1'. The function 'method2' is used to handle 'OPERATOR2'. etc. The order of the operator in this list is the priority order: in case the same operator appear multiple times in the list, the first one is the priority.

It is used to perform the association between the operation on a type and the function that performs this operation. Without an oplist, M*LIB has no way to known how to deal with your type and will deal with it like a classic C type.

When you define an instance of a new container, you give the type oplist you want to use as the base of the container. This type oplist performs the association between the operators and the methods for the type. In function of the available interface of the oplist, the container definition macro function generates the interface of the container. You can then use this interface directly. You can also use the oplist of the container to chain this new interface with another container, creating container-of-container. oplist and definition

A function name can be followed by the token M_IPTR in the oplist (for example: (INIT(init_func M_IPTR)) ) to specify that the function accept as its first argument a pointer to the type rather than the type itself (aka the prototype is init_func(type *) instead of init_func(type)). If you use the '[1]' trick (see below), you won't need this.

An oplist has no real form from a C language point of view. It is just an abstraction that disappears after the macro expansion step of the preprocessing.

For each object / container, an oplist is needed and the following operators are expected for an object otherwise default constructors are used:

  • INIT constructor(obj): initialize the object 'obj' into a valid state.
  • INIT_SET constructor(obj, org): initialize the object 'obj' into the same state as the object 'org'.
  • SET operator(obj, org): set the initialized object 'obj' into the same state as the initialized object org.
  • CLEAR destructor(obj): destroy the initialized object 'obj', releasing any attached memory. This method shall never fail.

INIT, INIT_SET & SET methods shall only fail due to memory errors.

Not all operators are needed within an oplist to handle a container. If some operator is missing, the associated default operator of the function is used. To use C integer or float types, the default constructors are perfectly fine: you may use M_DEFAULT_OPLIST to define the operator list of such types or you can just omit it.

NOTE: An iterator doesn't have a constructor nor destructor methods and it shall not allocate any memory.

Other documented operators are:

  • NEW (type) -> type pointer: allocate a new object with suitable alignment and size. The returned object is not initialized. INIT operator shall be called afterward. The default is M_MEMORY_ALLOC.
  • DEL (&obj): free the allocated uninitialized object 'obj' (default is M_MEMORY_DEL). The object is not cleared before being free (CLEAR operator has to be called before). The object shall have been allocated by the NEW operator.
  • REALLOC(type, type pointer, number) --> type pointer: realloc the given type pointer (either NULL or a pointer returned by the REALLOC operator itself) to an array of number objects of this type. Previously objects pointed by the pointer are kept up to the minimum of the new or old array size. New objects are not initialized (INIT operator has to be called afterward). Freed objects are not cleared (CLEAR operator has to be called before). The default is M_MEMORY_REALLOC.
  • FREE (&obj) : free the allocated uninitialized array object 'obj' (default is M_MEMORY_FREE). The objects are not cleared before being free (CLEAR operator has to be called before). The object shall have been allocated by the REALLOC operator.
  • INIT_MOVE(objd, objc): Initialize 'objd' to the same state than 'objc' by stealing as resources as possible from 'objc', and then clear 'objc'. It is semantically equivalent to calling INIT_SET(objd,objc) then CLEAR(objc) (but usually way faster). By default, all objects are assumed to be trivially movable (i.e. using memcpy to move an object is safe). Most C objects are trivially movable. If an object is not trivially movable, it shall provide an INIT_MOVE method or disable the INIT_MOVE method entirely.
  • MOVE(objd, objc): Set 'objd' to the same state than 'objc' by stealing as resources as possible from 'objc' and then clear 'objc'. It is equivalent to calling SET(objd,objc) then CLEAR(objc) or CLEAR(objd) and then INIT_MOVE(objd, objc). TBC if operator is really needed as calling CLEAR then INIT_MOVE is what does all known implementation.
  • INIT_WITH(obj,...): Initialize the object 'obj' with a variable arguments. Arguments can be of different types. This is used within the M_LET macro to initialize objects with values.
  • SWAP(objd, objc): Swap the object 'objc' and the object 'objd' states.
  • CLEAN(obj): Empty the container from all its objects. Nearly like CLEAR except that the container 'obj' remains initialized (but empty).
  • HASH (obj) --> size_t: return a hash of the object (usable for a hash table). Default is performing a hash of the memory representation of the object (which may be invalid is the object holds pointer to other objects).
  • EQUAL(obj1, obj2) --> bool: return true if both objects are equal, false otherwise. Default is using the C comparison operator.
  • CMP(obj1, obj2) --> int: return a negative integer if obj1 < obj2, 0 if obj1 = obj2, a positive integer otherwise. Default is C comparison operators.
  • ADD(obj1, obj2, obj3) : set obj1 to the sum of obj2 and obj3. Default is '+' C operator.
  • SUB(obj1, obj2, obj3) : set obj1 to the difference of obj2 and obj3. Default is '-' C operator.
  • MUL(obj1, obj2, obj3) : set obj1 to the product of obj2 and obj3. Default is '*' C operator.
  • DIV(obj1, obj2, obj3) : set obj1 to the division of obj2 and obj3. Default is '/' C operator.
  • KEY_TYPE() --> key_t: Return the type of key for associative containers.
  • VALUE_TYPE() --> valye_t: Return the type of value for associative containers.
  • KEY_OPLIST() --> oplist: Return the oplist of the key for associative containers.
  • VALUE_OPLIST() --> oplist: Return the oplist of the value for associative containers.
  • GET_KEY (container, key) --> &obj: return a pointer to the object within the container associated to the key 'key' or return NULL if no object is associated to this key. The pointer to the object remains valid until any modification of the container.
  • SET_KEY (container, key, object): Associate in the container the key 'key' to the object 'object'.
  • GET_SET_KEY (container, key) --> &obj: return a pointer to the object within the container associated to the key 'key' or create a new object in the container, associate it to the key 'key' and return its pointer. The pointer to the object remains valid until any modification of the container. The returned pointer cannot be NULL.
  • ERASE_KEY (container, key) --> bool: true to erase the object associated to the key 'key' within the container. Return true if successful, false if the key is not found.
  • GET_SIZE (container) --> size_t: return the number of elements in the container.
  • PUSH(container, obj) : push 'object' into 'container'. How it is pushed is container dependent.
  • POP(&obj, container) : pop an object from 'container' and save it in '*obj' if obj is not NULL (giving back the ownership to the caller). Which object is popped is container dependent. Undefined behavior is there is no object in the container.
  • PUSH_MOVE(container, &obj) : push and move the object '*obj' into 'container'. How it is pushed is container dependent but '*obj' is cleared afterward.
  • POP_MOVE(&obj, container) : pop an object from 'container' and init & move it in '*obj'. Which object is popped is container dependent. '*obj' shall be uninitialized. Undefined behavior is there is no object in the container.
  • SPLICE_BACK(containerDst, containerSrc, it): move the object referenced by the iterator 'it' from 'containerSrc' into 'containerDst'. Where is move is container dependent. Afterward 'it' points to the next item in 'containerSrc'.
  • SPLICE_AT(containerDst, itDst, containerSrc, itSrc): move the object referenced by the iterator 'itSrc' from 'containerSrc' just after the object referenced by the iterator 'itDst' in 'containerDst'. If 'itDst' doesn't reference a valid object, it is inserted as the first item of the container (See method 'INSERT'). Afterward 'itSrc' references the next item in 'containerSrc'and 'itDst' references the moved item in 'containerDst'.
  • TYPE() --> type: return the type associated to this oplist.
  • SUBTYPE() --> type: return the type of the element stored in the container.
  • OPLIST() --> oplist: return the oplist of the type of the elements stored in the container.
  • IT_TYPE() --> type: return the type of an iterator object of this container.
  • IT_FIRST(it_obj, container): set the object iterator it_obj to the first sub-element of container.
  • IT_LAST(it_obj, container): set the object iterator it_obj to the last sub-element of container.
  • IT_END(it_obj, container): set the object iterator it_obj to the end of the container (Can't use PREVIOUS or NEXT afterward).
  • IT_SET(it_obj, it_obj2): set the object iterator it_obj to it_obj2.
  • IT_END_P(it_obj)--> bool: return true if it_obj references an end of the container.
  • IT_LAST_P(it_obj)--> bool: return true if the iterator it_obj has reached an end or if the iterator points to the last element (just before the end).
  • IT_EQUAL_P(it_obj, it_obj2) --> bool: return true if both iterators references the same element.
  • IT_NEXT(it_obj): move the iterator to the next sub-component.
  • IT_PREVIOUS(it_obj): move the iterator to the previous sub-component.
  • IT_CREF(it_obj) --> &obj: return a constant pointer to the object referenced by the iterator.
  • IT_REF(it_obj) --> &obj: return a pointer to the object referenced by the iterator.
  • IT_REMOVE(container, it_obj): remove it_obj from the container (clearing it) and update it_obj to point to the next object. All other iterators of the same container become invalidated.
  • OUT_STR(FILE* f, obj): Output 'obj' as a string into the FILE stream 'f'.
  • IN_STR(obj, FILE* f) --> bool: Set 'obj' to the string object in the FILE stream 'f'. Return true in case of success (in that case the stream 'f' has been advanced to the end of the parsing of the object), false otherwise (in that case, the stream 'f' is in an undetermined position).
  • GET_STR(string_t str, obj, bool append): Set 'str' to a string representation of the object 'obj'. Append to the string if 'append' is true, set it otherwise.
  • PARSE_STR(obj, const char *str, const char **endp) --> bool: Set 'obj' to the string object in the char stream 'str'. Return true in case of success (in that case if endp is not NULL, it points to the end of the parsing of the object), false otherwise (in that case, if endp is not NULL, it points to an undetermined position).
  • UPDATE(dest, src): Update 'dest' with 'src'. What it does exactly is node dependent: it can either SET or ADD to the node the new 'src' (default is SET).
  • OOR_SET(obj, int_value): some containers may want to store some information within some uninitialized objects (for example Open Addressing Hash Table). This method will store the integer value 'int_value' into the uninitialized object 'obj'. The way to store this information is object dependent. In general, you use out-of-range value for detecting such values. The object remains uninitialized but set to of out-of-range value. int_value values can be 0 or 1.
  • OOR_EQUAL(obj, int_value): This method will compare the object 'obj' to the out-of-range value used to represent int_value and return true if both objects are equal.

More operators are expected.

Example:

    (INIT(mpz_init),SET(mpz_set),INIT_SET(mpz_init_set),CLEAR(mpz_clear))

If there is two operations with the same name in an oplist, the left one has the priority. This allows partial overriding.

Some pre-defined oplist exist:

  • M_DEFAULT_OPLIST: Oplist for a C type (integer or float),
  • M_POD_OPLIST: Oplist for a plain structure (not an array type),
  • M_A1_OPLIST: Oplist for a structure defined as an array of size 1,
  • M_CLASSIC_OPLIST(name): Oplist for a type that provides standard functions: name##_init, name##_init_set, name##_set, name##_clear.
  • M_CSTR_OPLIST: Oplist for a string represented by a const char pointer.
  • M_PTR_OPLIST: Oplist for a plain pointer.

Oplists can be registered globally by defining for the type 'type' a macro named M_OPL_ ## type () that expands to the oplist of the type. Only type without space in their name can be registered. A typedef of the type can be used instead through.

Example:

    #define M_OPL_mpz_t() M_CLASSIC_OPLIST(mpz_t)

Within an OPLIST, you can specify the API needed transformation to perform for the method. Assuming that the method to call is call 'method' and the first argument of the operator is 'output', then the following transformation are applied:

  • API_0: method(output, ...) /* Default API */
  • API_1: method(oplist, output, ...) /* Give oplist to the method */
  • API_2: method(&output, ...) /* Pass by address the first argument (like with M_IPTR) */
  • API_3: method(oplist, &output, ...) /* Pass by address the first argument (like with M_IPTR) and give the oplist of the type */
  • API_4 : output = method(...) /* Pass by return value the first argument */
  • API_5: output = method(oplist, ...) /* Pass by return value the first argument and give the oplist of the type*/

Example:

    (INIT(API_0(mpz_init)), SET(API_0(mpz_set)), INIT_SET(API_0(mpz_init_set)), CLEAR(API_0(mpz_clear)))

Memory Allocation

Memory Allocation functions can be globally set by overriding the following macros before using the definition _DEF macros:

  • M_MEMORY_ALLOC (type): return a pointer to a new object of type 'type'.
  • M_MEMORY_DEL (ptr): free the single object pointed by 'ptr'.
  • M_MEMORY_REALLOC (type, ptr, number): return a pointer to an array of 'number' objects of type 'type', reusing the old array pointed by 'ptr'. 'ptr' can be NULL, in that case the array will be created.
  • M_MEMORY_FREE (ptr): free the array of objects pointed by 'ptr'.

ALLOC & DEL operators are supposed to allocate fixed size single element object (no array). Theses objects are not expected to grow. REALLOC & FREE operators deal with allocated memory for growing objects. Do not mix pointers between both: a pointer allocated by ALLOC (resp. REALLOC) is supposed to be only freed by DEL (resp. FREE). There are separated 'free' operators to allow specialization in the allocator (a good allocator can take this hint into account).

M_MEMORY_ALLOC and M_MEMORY_REALLOC are supposed to return NULL in case of memory allocation failure. The defaults are 'malloc', 'free', 'realloc' and 'free'.

You can also override the methods NEW, DEL, REALLOC & DEL in the oplist given to a container so that only the container will use these memory allocation functions.

Out-of-memory error

When a memory exhaustion is reached, the global macro "M_MEMORY_FULL" is called and the function returns immediately after. The object remains in a valid (if previously valid) and unchanged state in this case. By default, the macro prints an error message and abort the program: handling non-trivial memory errors can be hard, testing them is even harder but still mandatory to avoid security holes. So the default behavior is rather conservative.

Moreover a good program design should handle a process entire failure (using for examples multiple processes for doing the job) so even a process stops can be recovered. See here for more information about why abandonment is good software practice.

It can however be overloaded to handle other policy for error handling like:

  • throwing an error (in that case, the user is responsible to free memory of the allocated objects - destructor can still be called),
  • set a global error and handle it when the function returns,
  • other policy.

This function takes the size in bytes of the memory that has been tried to be allocated.

If needed, this macro shall be defined prior to instantiate the structure.

NOTE: Throwing an error is not fully supported yet. Some help from the library is needed to avoid losing memory. See issue #15.

ERRORS & COMPILERS

When defining the oplist of a type using M*LIB, sometimes (often) the list of errors/warnings generated by the compiler can be (very) huge (specially with latest compilers), even if the error itself is minor. This is more or less the same as the use of template in C++. You should focus mainly on the first reported error/warning even if the link between what the compiler report and what the error is is not immediate. The error is always in the oplist definition. Examples of typical errors:

  • lack of inclusion of an header,
  • overriding locally operator names by macros (like NEW, DEL, INIT, OPLIST, ...),
  • lack of ( ) or double level of ( ) around the oplist,
  • an unknown variable (example using DEFAULT_OPLIST instead of M_DEFAULT_OPLIST),
  • a missing argument,
  • a missing mandatory operator in the oplist.

Another way to debug is to generate the preprocessed file (by usually calling the compiler with the '-E' option instead of '-c') and check what's wrong in the preprocessed file:

      cc -std=c99 -E test-file.c > test-file.i
      perl -pi -e 's/;/;\n/g' test-file.i
      cc -std=c99 -c -Wall test-file.i

If there is a warning reported by the compiler in the generated code, then there is definitely an error you should fix (except if it reports shadowed variables).

You should use global oplist to centralize the oplist definition in only one place.

Benchmarks

All bench codes are available in the bench directory. The results are available in a separate page.

External Reference

Many other implementation of generic container libraries in C exist. For example:

Each can be classified into one of the following concept:

  • Each object is handled through a pointer to void (with potential registered callbacks to handle the contained objects for the specialized methods). From a user point of view, this makes the code harder to use (as you don't have any help from the compiler) and type unsafe with a lot of cast (so no formal proof of the code is possible). This also generally generate slower code (even if using link time optimization, this penalty can be reduced). Properly used, it can yet be the most space efficient for the code, but can consume a lot more for the data due to the obligation of using pointers. This is however the easiest to design & code.
  • Macros are used to access structures in a generic way (using known fields of a structure - typically size, number, etc.). From a user point of view, this can create subtitle bug in the use of the library (as everything is done through macro expansion in the user defined code) or hard to understand warnings. This can generates fully efficient code. From a library developer point of view, this can be quite limiting in what you can offer.
  • A known structure is put in an intrusive way in the type of all the objects you wan to handle. From a user point of view, he needs to modify its structure and has to perform all the allocation & deallocation code itself (which is good or bad depending on the context). This can generate efficient code (both in speed and size). From a library developer point of view, this is easy to design & code. You need internally a cast to go from a pointer to the known structure to the pointed object (a reverse of offsetof) that is generally type unsafe (except if mixed with the macro generating concept). This is quite limitation in what you can do: you can't move your objects so any container that has to move some objects is out-of-question (which means that you cannot use the most efficient container).
  • Header files are included multiple times with different contexts (some different values given to defined macros) in order to generate different code for each type. From a user point of view, this creates a new step before using the container: an instantiating stage that has to be done once per type and per compilation unit (The user is responsible to create only one instance of the container, which can be troublesome if the library doesn't handle proper prefix for its naming convention). The debug of the library is generally easy and can generate fully specialized & efficient code. Incorrectly used, this can generate a lot of code bloat. Properly used, this can even create smaller code than the void pointer variant. The interface used to configure the library can be quite tiresome in case of a lot of specialized methods to configure: it doesn't allow to chain the configuration from a container to another one easily. It also doesn't allow heavy customization of the code.
  • Macros are used to generate context-dependent C code allowing to generate code for different type. This is pretty much like the headers solution but with added flexibility. From a user point of view, this creates a new step before using the container: an instantiating stage that has to be done once per type and per compilation unit (The user is responsible to create only one instance of the container, which can be troublesome if the library doesn't handle proper prefix for its naming convention). This can generate fully specialized & efficient code. Incorrectly used, this can generate a lot of code bloat. Properly used, this can even create smaller code than the void pointer variant. From a library developer point of view, the library is harder to design and to debug: everything being expanded in one line, you can't step in the library (there is however a solution to overcome this limitation by adding another stage to the compilation process). You can however see the generated code by looking at the preprocessed file. You can perform heavy context-dependent customization of the code (transforming the macro preprocessing step into its own language). Properly done, you can also chain the methods from a container to another one easily, allowing expansion of the library. Errors within the macro expansion are generally hard to decipher, but errors in code using containers are easy to read and natural.

M*LIB's category is mainly the last one. Some macros are also defined to access structure in a generic way, but they are optional. There are also intrusive containers. M*LIB main added value compared to other libraries is its oplist feature allowing it to chain the containers and/or use complex types in containers: list of array of dictionary are perfectly supported by M*LIB.

For the macro-preprocessing part, other libraries also exist. For example:

For the string library, there is for example:

API Documentation

The M*LIB reference card is available here.

M-LIST

This header is for creating singly linked list.

A linked list is a linear collection of elements, in which each element points to the next, all representing a sequence.

LIST_DEF(name, type, [, oplist])

Define the singly linked list named 'name##_t' that contains objects of type 'type' and the associated methods as "static inline" functions. 'name' shall be a C identifier that will be used to identify the list. It will be used to create all the types and functions to handle the container. This definition shall be done once per name and per compilation unit. It also define the iterator name##_it_t and its associated methods as "static inline" functions.

A fundamental property of a list is that the objects created within the list will remain at their initialized address, and won't moved due to operations done on the list (except if it is removed).

The type oplist is expected to have at least the following operators (INIT_SET, SET and CLEAR). If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used if there is one available. The created methods use the operators to init, set and clear the contained object.

For this structure, the back is always the first element, and the front is the last element: the list grows from the back.

Example:

LIST_DEF(list_uint, unsigned int)

list_uint_t list_of_integer;

void fi(unsigned int z) {
	list_uint_push_back (list_of_integer, z);
}
    
LIST_DEF(list_mpz, mpz_t,                                               \
	(INIT(mpz_init), INIT_SET(mpz_init_set), SET(mpz_set), CLEAR(mpz_clear)))

list_mpz_t my_list;

void fz(mpz_t z) {
	list_mpz_push_back (my_list, z);
}

If the given oplist contain the method MEMPOOL, then LIST_DEF macro will create a dedicated mempool that is named with the given value of the method MEMPOOL. This mempool (see mempool chapter) is optimized for this kind of list:

  • it creates a mempool named by the concatenation of "name" and "_mempool",
  • it creates a variable named by the value of the method MEMPOOL with the linkage defined by the value of the method MEMPOOL_LINKAGE (can be extern, static or none). This variable is shared by all lists of the same type.
  • it links the memory allocation of the list to use this mempool with this variable.

Using mempool allows to create heavily efficient list. However it is only worth the effort in some heavy performance context. The created mempool has to be explictely initialized before using any methods of the created list by calling mempool_list_name_init(variable) and cleared by calling mempool_list_name_clear(variable).

Example:

    LIST_DEF(list_uint, unsigned int, (MEMPOOL(list_mpool), MEMPOOL_LINKAGE(static)))

    static void test_list (size_t n)
    {
      list_uint_mempool_init(list_mpool);
      M_LET(a1, LIST_OPLIST(uint)) {
          for(size_t i = 0; i < n; i++)
              list_uint_push_back(a1, rand_get() );
      }
      list_uint_mempool_clear(list_mpool);
    }

LIST_OPLIST(name [, oplist])

Return the oplist of the list defined by calling LIST_DEF & LIST_DUAL_PUSH_DEF with name & oplist. If there is no given oplist, the default oplist for standard C type is used.

Created methods

In the following methods, name stands for the name given to the macro that is used to identify the type. The following types are automatically defined by the previous definition macro:

name_t

Type of the list of 'type'.

name_it_t

Type of an iterator over this list.

The following methods are automatically created by the previous definition macro:

void name_init(name_t list)

Initialize the list 'list' (aka constructor) to an empty list.

void name_init_set(name_t list, const name_t ref)

Initialize the list 'list' (aka constructor) and set it to a copy of 'ref'.

void name_set(name_t list, const name_t ref)

Set the list 'list' to the a copy of 'ref'.

void name_init_move(name_t list, name_t ref)

Initialize the list 'list' (aka constructor) by stealing as many resources from 'ref' as possible. After-wise 'ref' is cleared and can no longer be used.

void name_move(name_t list, name_t ref)

Set the list 'list' (aka constructor) by stealing as many resources from 'ref' as possible. After-wise 'ref' is cleared and can no longer be used.

void name_clear(name_t list)

Clear the list 'list (aka destructor), calling the CLEAR method of all the objects of the list and freeing memory. The list can't be used anymore, except with a constructor.

void name_clean(name_t list)

Clean the list (the list becomes empty). It is like CLEAR but the list remains initialized and empty.

const type *name_back(const name_t list)

Return a constant pointer to the data stored in the back of the list.

void name_push_back(name_t list, const type value)

Push a new element within the list 'list' with the value 'value' contained within.

type *name_push_raw(name_t list)

Push a new element within the list 'list' without initializing it and returns a pointer to the non-initialized data. The first thing to do after calling this function is to initialize the data using the proper constructor. This enables using more specialized constructor than the generic one. Return a pointer to the non-initialized data.

type *name_push_new(name_t list)

Push a new element within the list 'list' and initialize it with the default constructor of the type. Return a pointer to the initialized object. This method is only defined if the type of the element defines an INIT method.

void name_push_move(name_t list, type *value)

Push a new element within the list 'list' with the value '*value' contained within by stealing as much resources from *value than possible. Afterwise *value is cleared and cannot longer be used.

void name_pop_back(type *data, name_t list)

Pop a element from the list 'list', and set *data to this value if data is not the NULL pointer (otherwise only pop the data).

void name_pop_move(type *data, name_t list)

Pop a element from the list 'list', and initialize and set *data to this value by stealing as much resources from the list as possible. data cannot be a NULL pointer.

bool name_empty_p(const name_t list)

Return true if the list is empty, false otherwise.

void name_swap(name_t list1, name_t list2)

Swap the list 'list1' and 'list2'.

void name_it(name_it_t it, const name_t list)

Set the iterator 'it' to the back(=first) element of 'list'. There is no destructor associated to this initialization.

void name_it_set(name_it_t it, const name_it_t ref)

Set the iterator 'it' to the iterator 'ref'. There is no destructor associated to this initialization.

void name_it_end(name_it_t it, const name_t list)

Set the iterator 'it' to a non valid element of the list. There is no destructor associated to this initialization.

bool name_end_p(const name_it_t it)

Return true if the iterator doesn't reference a valid element anymore.

bool name_last_p(const name_it_t it)

Return true if the iterator references the top(=last) element or if the iterator doesn't reference a valid element anymore.

bool name_it_equal_p(const name_it_t it1, const name_it_t it2)

Return true if the iterator it1 references the same element than it2.

void name_next(name_it_t it)

Move the iterator 'it' to the next element of the list, i.e. from the back (=first) element to the front (=last) element.

type *name_ref(name_it_t it)

Return a pointer to the element pointed by the iterator. This pointer remains valid until the element is destroyed in the list.

const type *name_cref(const name_it_t it)

Return a constant pointer to the element pointed by the iterator. This pointer remains valid until the element is destroyed in the list.

type *name_get(const name_t list, size_t i)

Return a pointer to the element i-th of the list (from 0). It is assumed than i is within the size of the list. This function is slow and iterates linearly over the element of the elements until it reaches the desired element.

const type *name_cget(const name_t list, size_t i)

Return a constant pointer to the element i-th of the list (from 0). It is assumed than i is within the size of the list. This function is slow and iterates linearly over the element of the elements until it reaches the desired element.

size_t name_size(const name_t list)

Return the number elements of the list (aka size). Return 0 if there no element. This function is slow and iterates linearly over all the element to compute the size.

void name_insert(name_t list, const name_it_t it, const type x)

Insert 'x' after the position pointed by 'it' (which is an iterator of the list 'list') or if 'it' doesn't point anymore to a valid element of the list, it is added as the back (=first) element of the 'list'

void name_remove(name_t list, name_it_t it)

Remove the element 'it' from the list 'list'. After wise, 'it' points to the next element of the list.

void name_splice_back(name_t list1, name_t list2, name_it_t it)

Move the element pointed by 'it' (which is an iterator of 'list2') from the list 'list2' to the back position of 'list1'. After wise, 'it' points to the next element of 'list2'.

void name_splice_at(name_t list1, name_it_t it1, name_t list2, name_it_t it2)

Move the element pointed by 'it2' (which is an iterator of 'list2') from the list 'list2' to the position just after 'it1' in the list 'list1'. After wise, 'it2' points to the next element of 'list2' and 'it1' points to the inserted element in 'list1'. If 'it1' is the end position, it inserts it at the back (just like _insert_at).

void name_splice(name_t list1, name_t list2)

Move all the element of 'list2' into 'list1", moving the last element of 'list2' after the first element of 'list1'. After-wise, 'list2' remains initialized but is emptied.

void name_reverse(name_t list)

Reverse the order of the list.

void name_get_str(string_t str, const name_t list, bool append)

Generate a string representation of the list 'list' and set 'str' to this representation (if 'append' is false) or append 'str' with this representation (if 'append' is true). This method is only defined if the type of the element defines a GET_STR method itself.

bool name_parse_str(name_t list, const char str[], const char **endp)

Parse the string 'str' that is assumed to be a string representation of a list and set 'list' to this representation. This method is only defined if the type of the element defines a PARSE_STR & INIT methods itself. It returns true if success, false otherwise. If endp is not NULL, it sets '*endp' to the pointer of the first character not decoded by the function.

void name_out_str(FILE *file, const name_t list)

Generate a string representation of the list 'list' and outputs it into the FILE 'file'. This method is only defined if the type of the element defines a OUT_STR method itself.

void name_in_str(name_t list, FILE *file)

Read from the file 'file' a string representation of a list and set 'list' to this representation. This method is only defined if the type of the element defines a IN_STR & INIT method itself.

bool name_equal_p(const name_t list1, const name_t list2)

Return true if both lists 'list1' and 'list2' are equal. This method is only defined if the type of the element defines a EQUAL method itself.

size_t name_hash(const name_t list)

Return the has value of 'list'. This method is only defined if the type of the element defines a HASH method itself.

LIST_DUAL_PUSH_DEF(name, type[, oplist])

Define the singly linked list named 'name##_t' that contains the objects of type 'type' and the associated methods as "static inline" functions. 'name' shall be a C identifier that will be used to identify the list. It will be used to create all the types and functions to handle the container. It shall be done once per type and per compilation unit. It also define the iterator name##_it_t and its associated methods as "static inline" functions too.

The only difference with the list defined by LIST_DEF is the support of the method PUSH_FRONT in addition to PUSH_BACK (therefore the DUAL PUSH name). There is still only POP method (POP_BACK). The head of the list is a bit bigger to be able to handle such methods to work. This enables this list to be able to represent both stack (PUSH_BACK + POP_BACK) and queue (PUSH_FRONT + POP_BACK).

A fundamental property of a list is that the objects created within the list will remain at their initialized address, and won't moved due to operations on the list.

The object oplist is expected to have at least the following operators (INIT_SET, SET and CLEAR). If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object.

For this structure, the back is always the first element, and the front is the last element.

Example:

LIST_DUAL\_PUSH\_DEF(list_mpz, mpz_t,                                   \
	(INIT(mpz_init), INIT_SET(mpz_init_set), SET(mpz_set), CLEAR(mpz_clear)))

list_mpz_t my_list;

void f(mpz_t z) {
	list_mpz_push_front (my_list, z);
	list_mpz_pop_back (z, my_list);
}

If the given oplist contain the method MEMPOOL, then macro will create a dedicated mempool that is named with the given value of the method MEMPOOL, optimized for this kind of list:

  • it creates a mempool named by the concatenation of "name" and "_mempool",
  • it creates a variable named by the value of the method MEMPOOL with linkage defined by the value of the method MEMPOOL_LINKAGE (can be extern, static or none), this variable will be shared by all lists of the same type.
  • it overwrites memory allocation of the created list to use this mempool with this variable.

Using mempool allows to create heavily efficient list but it will be only worth the effort in some heavy performance context. The created mempool has to be initialized before using any methods of the created list by calling mempool_list_name_init(variable) and cleared by calling mempool_list_name_clear(variable).

The methods follow closely the methods defined by LIST_DEF.

Created methods

In the following methods, name stands for the name given to the macro that is used to identify the type. The following types are automatically defined by the previous macro:

name_t

Type of the list of 'type'.

name_it_t

Type of an iterator over this list.

The following methods are automatically and properly created by the previous macro.

void name_init(name_t list)

Initialize the list 'list' (aka constructor) to an empty list.

void name_init_set(name_t list, const name_t ref)

Initialize the list 'list' (aka constructor) and set it to the value of 'ref'.

void name_set(name_t list, const name_t ref)

Set the list 'list' to the value of 'ref'.

void name_init_move(name_t list, name_t ref)

Initialize the list 'list' (aka constructor) by stealing as many resources from 'ref' as possible. After-wise 'ref' is cleared and can no longer be used.

void name_move(name_t list, name_t ref)

Set the list 'list' (aka constructor) by stealing as many resources from 'ref' as possible. After-wise 'ref' is cleared and can no longer be used.

void name_clear(name_t list)

Clear the list 'list (aka destructor). The list can't be used anymore, except with a constructor.

void name_clean(name_t list)

Clean the list (the list becomes empty). The list remains initialized but is empty.

const type *name_back(const name_t list)

Return a constant pointer to the data stored in the back of the list.

void name_push_back(name_t list, type value)

Push a new element within the list 'list' with the value 'value' into the back of the list.

type *name_push_back_raw(name_t list)

Push a new element within the list 'list' without initializing it into the back of the list and returns a pointer to the non-initialized data. The first thing to do after calling this function is to initialize the data using the proper constructor. This allows to use a more specialized constructor than the generic one. Return a pointer to the non-initialized data.

type *name_push_back_new(name_t list)

Push a new element within the list 'list' into the back of the list and initialize it with the default constructor of the type. Return a pointer to the initialized object. This method is only defined if the type of the element defines an INIT method.

void name_push_back_move(name_t list, type *value)

Push a new element within the list 'list' with the value '*value' ,by stealing as much resources from '*value' as possible, into the back of the list. Afterwards, *value is cleared and cannot be used anymore. value cannot be NULL.

const type *name_front(const name_t list)

Return a constant pointer to the data stored in the front of the list.

void name_push_front(name_t list, type value)

Push a new element within the list 'list' with the value 'value' contained within into the front of the list.

type *name_push_front_raw(name_t list)

Push a new element within the list 'list' without initializing it into the front of the list and returns a pointer to the non-initialized data. The first thing to do after calling this function is to initialize the data using the proper constructor. This allows to use a more specialized constructor than the generic one. Return a pointer to the non-initialized data.

type *name_push_front_new(name_t list)

Push a new element within the list 'list' into the front of the list and initialize it with the default constructor of the type. Return a pointer to the initialized object. This method is only defined if the type of the element defines an INIT method.

void name_push_front_move(name_t list, type *value)

Push a new element within the list 'list' with the value '*value' ,by stealing as much resources from '*value' as possible, into the front of the list. Afterwards, *value is cleared and cannot be used anymore. value cannot be NULL.

void name_pop_back(type *data, name_t list)

Pop a element from the list 'list' and set *data to this value if data is not NULL.

void name_pop_move(type *data, name_t list)

Pop a element from the list 'list' and initialize and set *data to this value, stealing as much resources from the list as possible.

bool name_empty_p(const name_t list)

Return true if the list is empty, false otherwise.

void name_swap(name_t list1, name_t list2)

Swap the list 'list1' and 'list2'.

void name_it(name_it_t it, name_t list)

Set the iterator 'it' to the back(=first) element of 'list'. There is no destructor associated to this initialization.

void name_it_set(name_it_t it, const name_it_t ref)

Set the iterator 'it' to the iterator 'ref'. There is no destructor associated to this initialization.

void name_it_end(name_it_t it, const name_t list)

Set the iterator 'it' to an invalid reference of 'list'. There is no destructor associated to this initialization.

bool name_end_p(const name_it_t it)

Return true if the iterator doesn't reference a valid element anymore.

bool name_last_p(const name_it_t it)

Return true if the iterator references the top(=last) element or if the iterator doesn't reference a valid element anymore.

bool name_it_equal_p(const name_it_t it1, const name_it_t it2)

Return true if the iterator it1 references the same element than it2.

void name_next(name_it_t it)

Move the iterator 'it' to the next element of the list, ie. from the back (=first) element to the front(=last) element.

type *name_ref(name_it_t it)

Return a pointer to the element pointed by the iterator. This pointer remains valid until the object is destroyed.

const type *name_cref(const name_it_t it)

Return a constant pointer to the element pointed by the iterator. This pointer remains valid until the object is destroyed.

size_t name_size(const name_t list)

Compute and return the number elements of the list (aka size). Return 0 if there no element.

void name_insert(name_t list, const name_it_t it, const type x)

Insert 'x' after the position pointed by 'it' (which is an iterator of the list 'list') or if 'it' doesn't point anymore to a valid element of the list, it is added as the back (=first) element of the 'list'

void name_remove(name_t list, name_it_t it)

Remove the element 'it' from the list 'list'. After wise, 'it' points to the next element of the list.

void name_splice_back(name_t list1, name_t list2, name_it_t it)

Move the element pointed by 'it' (which is an iterator of 'list2') from the list 'list2' to the back position of 'list1'. After wise, 'it' points to the next element of 'list2'.

void name_splice(name_t list1, name_t list2)

Move all the element of 'list2' into 'list1", moving the last element of 'list2' after the first element of 'list1'. After-wise, 'list2' is emptied.

void name_reverse(name_t list)

Reverse the order of the list.

void name_get_str(string_t str, const name_t list, bool append)

Generate a string representation of the list 'list' and set 'str' to this representation (if 'append' is false) or append 'str' with this representation (if 'append' is true). This method is only defined if the type of the element defines a GET_STR method itself.

bool name_parse_str(name_t list, const char str[], const char **endp)

Parse the string 'str' that is assumed to be a string representation of a list and set 'list' to this representation. This method is only defined if the type of the element defines PARSE_STR & INIT methods itself. It returns true if success, false otherwise. If endp is not NULL, it sets '*endp' to the pointer of the first character not decoded by the function.

void name_out_str(FILE *file, const name_t list)

Generate a string representation of the list 'list' and outputs it into the FILE 'file'. This method is only defined if the type of the element defines a OUT_STR method itself.

void name_in_str(name_t list, FILE *file)

Read from the file 'file' a string representation of a list and set 'list' to this representation. This method is only defined if the type of the element defines a IN_STR & INIT method itself.

bool name_equal_p(const name_t list1, const name_t list2)

Return true if both lists 'list1' and 'list2' are equal. This method is only defined if the type of the element defines a EQUAL method itself.

size_t name_hash(const name_t list)

Return the has value of 'list'. This method is only defined if the type of the element defines a HASH method itself.

M-ARRAY

An array is a growable collection of element that are individually indexable.

ARRAY_DEF(name, type [, oplist])

Define the array 'name##_t' that contains the objects of type 'type' and its associated methods as "static inline" functions. Compared to C arrays, the created methods handle automatically the size (aka growable array). 'name' shall be a C identifier that will be used to identify the container.

It also define the iterator name##_it_t and its associated methods as "static inline" functions.

The object oplist is expected to have at least the following operators (INIT_SET, SET and CLEAR), otherwise default operators are used. If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object.

Example:

ARRAY_DEF(array_mpfr_t, mpfr,                                                                  \
   (INIT(mpfr_init), INIT_SET(mpfr_init_set), SET(mpfr_set), CLEAR(mpfr_clear)))

array_mpfr_t my_array;

void f(mpfr_t z) {
	array_mpfr_push_back (my_array, z);
}

ARRAY_OPLIST(name [, oplist])

Return the oplist of the array defined by calling ARRAY_DEF with name & oplist. If there is no given oplist, the default oplist for standard C type is used.

Created methods

In the following methods, name stands for the name given to the macro. This is used to identify the type. The following types are automatically defined by the previous macro:

name_t

Type of the array of 'type'.

name_it_t

Type of an iterator over this array.

The following methods are automatically and properly created by the previous macros:

void name_init(name_t array)

Initialize the array 'array' (aka constructor) to an empty array.

void name_init_set(name_t array, const name_t ref)

Initialize the array 'array' (aka constructor) and set it to the value of 'ref'.

void name_set(name_t array, const name_t ref)

Set the array 'array' to the value of 'ref'.

void name_init_move(name_t array, name_t ref)

Initialize the array 'array' (aka constructor) by stealing as many resources from 'ref' as possible. After-wise 'ref' is cleared.

void name_move(name_t array, name_t ref)

Set the array 'array' by stealing as many resources from 'ref' as possible. After-wise 'ref' is cleared.

void name_clear(name_t array)

Clear the array 'array (aka destructor).

void name_clean(name_t array)

Clean the array (the array becomes empty but remains initialized).

type *name_push_raw(name_t array)

Push the needed storage of a new element into the back of the array 'array' without initializing it and return a pointer to the non-initialized data. The first thing to do after calling this function is to initialize the data using the proper constructor. This allows to use a more specialized constructor than the generic one. It is recommended to use other _push function if possible rather than this one as it is low level and error prone.

void name_push_back(name_t array, const type value)

Push a new element into the back of the array 'array' with the value 'value' contained within.

type *name_push_new(name_t array)

Push a new element into the back of the array 'array' and initialize it with the default constructor. Return a pointer to this created element. This method is only defined if the type of the element defines an INIT method.

void name_push_move(name_t array, type *val)

Push '*val' a new element into the back of the array 'array' by stealing as much resources as possible from '*val'. After-wise '*x' is cleared.

void name_push_at(name_t array, size_t key, const type x)

Push a new element into the position 'key' of the array 'array' with the value 'value' contained within. 'key' shall be a valid position of the array: from 0 to the size of array (included).

void name_pop_back(type *data, name_t array)

Pop a element from the back of the array 'array' and set *data to this value if data is not NULL (if data is NULL, the popped data is cleared).

void name_pop_move(type *data, name_t array)

Pop a element from the back of the array 'array' and initialize *data with this value by stealing as much from the array as possible.

void name_pop_until(name_t array, array_it_t position)

Pop all elements of the array 'array' from 'position' to the back of the array, clearing them. This method is only defined if the type of the element defines an INIT method.

void name_pop_at(type *dest, name_t array, size_t key)

Set *dest to the value the element 'key' if dest is not NULL, then remove the element 'key' from the array. 'key' shall be within the size of the array.

const type *name_front(const name_t array)

Return a constant pointer to the first element of the array.

const type *name_back(const name_t array)

Return a constant pointer to the last element of the array.

void name_set_at(name_t array, size_t i, type value)

Set the element 'i' of array 'array' to 'value'. 'i' shall be within 0 to the size of the array (excluded).

type *name_get(name_t array, size_t i)

Return a pointer to the element 'i' of the array. 'i' shall be within 0 to the size of the array (excluded). The returned pointer cannot be NULL. This pointer remains valid until the array is modified by another method.

const type *name_cget(const name_t it, size_t i)

Return a constant pointer to the element 'i' of the array. 'i' shall be within 0 to the size of the array (excluded). The returned pointer cannot be NULL. This pointer remains valid until the array is modified by another method.

type *name_get_at(name_t array, size_t i)

Return a pointer to the element 'i' of array 'array', by increasing the size of the array if needed (creating new elements with INIT). The returned pointer cannot be NULL. This method is only defined if the type of the element defines an INIT method. This pointer remains valid until the array is modified by another method.

bool name_empty_p(const name_t array)

Return true if the array is empty, false otherwise.

size_t name_size(const name_t array)

Return the size of the array.

size_t name_capacity(const name_t array)

Return the capacity of the array.

void name_resize(name_t array, size_t size)

Resize the array 'array' to the size 'size' (initializing or clearing elements). This method is only defined if the type of the element defines an INIT method.

void name_reserve(name_t array, size_t capacity)

Extend or reduce the capacity of the 'array' to a rounded value based on 'capacity'. If the given capacity is below the current size of the array, the capacity is set to the size of the array.

void name_remove(name_t array, name_it_t it)

Remove the element pointed by the iterator 'it' from the array 'array'. 'it' shall be a valid iterator. Afterward 'it' points to the next element, or points to the end.

void name_remove_v(name_t array, size_t i, size_t j)

Remove the element 'i' (included) to the element 'j' (excluded) from the array. 'i' and 'j' shall be within the size of the array, and i < j.

void name_insert(name_t array, name_it_t it, const type x)

Insert the object 'x' at the position 'it' of the array. 'it' shall be a valid iterator of the array.

void name_insert_v(name_t array, size_t i, size_t j)

Insert from the element 'i' (included) to the element 'j' (excluded) new empty elements to the array. 'i' and 'j' shall be within the size of the array, and i < j. This method is only defined if the type of the element defines an INIT method.

void name_swap(name_t array1, name_t array2)

Swap the array 'array1' and 'array2'.

void name_swap_at(name_t array, size_t i, size_t j)

Swap the elements 'i' and 'j' of the array 'array'. 'i' and 'j' shall reference valid elements of the array.

void name_it(name_it_t it, name_t array)

Set the iterator 'it' to the first element of 'array'.

void name_it_last(name_it_t it, name_t array)

Set the iterator 'it' to the last element of 'array'.

void name_it_end(name_it_t it, name_t array)

Set the iterator 'it' to the end of 'array'.

void name_it_set(name_it_t it1, name_it_t it2)

Set the iterator 'it1' to 'it2'.

bool name_end_p(name_it_t it)

Return true if the iterator doesn't reference a valid element anymore.

bool name_last_p(name_it_t it)

Return true if the iterator references the last element of the array, or doesn't reference a valid element.

bool name_it_equal_p(const name_it_t it1, const name_it_t it2)

Return true if both iterators reference the same element.

void name_next(name_it_t it)

Move the iterator 'it' to the next element of the array.

void name_previous(name_it_t it)

Move the iterator 'it' to the previous element of the array.

type *name_ref(name_it_t it)

Return a pointer to the element pointed by the iterator. This pointer remains valid until the array is modified by another method.

const type *name_cref(const name_it_t it)

Return a constant pointer to the element pointed by the iterator. This pointer remains valid until the array is modified by another method.

void name_special_sort(name_t array)

Sort the array 'array'. This method is defined if the type of the element defines CMP methods. This method uses the qsort function of the C library.

void name_special_stable_sort(name_t array)

Sort the array 'array' using a stable sort. This method is defined if the type of the element defines CMP and SWAP methods. This method provides an ad-hoc implementation of the stable sort. In practice, it is faster than the _sort method for small types and fast comparisons.

void name_get_str(string_t str, const name_t array, bool append)

Generate a string representation of the array 'array' and set 'str' to this representation (if 'append' is false) or append 'str' with this representation (if 'append' is true). This method is only defined if the type of the element defines a GET_STR method itself.

bool name_parse_str(name_t array, const char str[], const char **endp)

Parse the string 'str' that is assumed to be a string representation of an array and set 'array' to this representation. This method is only defined if the type of the element defines both PARSE_STR & INIT methods itself. It returns true if success, false otherwise. If endp is not NULL, it sets '*endp' to the pointer of the first character not decoded by the function.

void name_out_str(FILE *file, const name_t array)

Generate a string representation of the array 'array' and outputs it into the FILE 'file'. This method is only defined if the type of the element defines a OUT_STR method itself.

void name_in_str(name_t array, FILE *file)

Read from the file 'file' a string representation of a array and set 'array' to this representation. This method is only defined if the type of the element defines both IN_STR & INIT methods itself.

bool name_equal_p(const name_t array1, const name_t array2)

Return true if both arrays 'array1' and 'array2' are equal. This method is only defined if the type of the element defines a EQUAL method itself.

size_t name_hash(const name_t array)

Return the has value of 'array'. This method is only defined if the type of the element defines a HASH method itself.

void name_splice(name_t array1, name_t array2)

Merge the elements of 'array2' in 'array1' at its end. Afterwards, 'array2' is empty.

M-DEQUE

This header is for creating double-ended queue (or deque). A deque is an abstract data type that generalizes a queue, for that elements can be added to or removed from either the front (head) or back (tail)

DEQUE_DEF(name, type, [, opdeque])

Define the deque 'name##_t' that contains the objects of type 'type' and its associated methods as "static inline" functions. 'name' shall be a C identifier that will be used to identify the deque. It will be used to create all the types and functions to handle the container. It shall be done once per type and per compilation unit. It also define the iterator name##_it_t and its associated methods as "static inline" functions.

The object oplist is expected to have at least the following operators (INIT, INIT_SET, SET and CLEAR), otherwise default operators are used. If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object.

Example:

DEQUE_DEF(deque_mpz, mpz_t,                                               \
	(INIT(mpz_init), INIT_SET(mpz_init_set), SET(mpz_set), CLEAR(mpz_clear)))

deque_mpz_t my_deque;

void f(mpz_t z) {
	deque_mpz_push_back (my_deque, z);
}

DEQUE_OPLIST(name [, oplist])

Return the oplist of the deque defined by calling DEQUE_DEF with name & oplist.

Created methods

In the following methods, name stands for the name given to the macro that is used to identify the type. The following types are automatically defined by the previous macro:

name_t

Type of the deque of 'type'.

name_it_t

Type of an iterator over this deque.

The following methods are automatically and properly created by the previous macro.

void name_init(name_t deque)

Initialize the deque 'deque' (aka constructor) to an empty deque.

void name_init_set(name_t deque, const name_t ref)

Initialize the deque 'deque' (aka constructor) and set it to the value of 'ref'.

void name_set(name_t deque, const name_t ref)

Set the deque 'deque' to the value of 'ref'.

void name_init_move(name_t deque, name_t ref)

Initialize the deque 'deque' (aka constructor) by stealing as many resources from 'ref' as possible. After-wise 'ref' is cleared and can no longer be used.

void name_move(name_t deque, name_t ref)

Set the deque 'deque' (aka constructor) by stealing as many resources from 'ref' as possible. After-wise 'ref' is cleared and can no longer be used.

void name_clear(name_t deque)

Clear the deque 'deque (aka destructor). The deque can't be used anymore, except with a constructor.

void name_clean(name_t deque)

Clean the deque (the deque becomes empty). The deque remains initialized but is empty.

const type *name_back(const name_t deque)

Return a constant pointer to the data stored in the back of the deque.

void name_push_back(name_t deque, type value)

Push a new element within the deque 'deque' with the value 'value' at the back of the deque.

type *name_push_back÷_raw(name_t deque)

Push at the back a new element within the deque 'deque' without initializing it and returns a pointer to the non-initialized data. The first thing to do after calling this function is to initialize the data using the proper constructor. This allows to use a more specialized constructor than the generic one. Return a pointer to the non-initialized data.

type *name_push_back_new(name_t deque)

Push at the back a new element within the deque 'deque' and initialize it with the default constructor of the type. Return a pointer to the initialized object.

void name_pop_back(type *data, name_t deque)

Pop a element from the deque 'deque' and set *data to this value. If data pointer is NULL, then the popped value is discarded.

const type *name_front(const name_t deque)

Return a constant pointer to the data stored in the front of the deque.

void name_push_front(name_t deque, type value)

Push at the front a new element within the deque 'deque' with the value 'value'.

type *name_push_front_raw(name_t deque)

Push at the front a new element within the deque 'deque' without initializing it and returns a pointer to the non-initialized data. The first thing to do after calling this function is to initialize the data using the proper constructor. This allows to use a more specialized constructor than the generic one. Return a pointer to the non-initialized data.

type *name_push_front_new(name_t deque)

Push at the front a new element within the deque 'deque' and initialize it with the default constructor of the type. Return a pointer to the initialized object.

void name_pop_front(type *data, name_t deque)

Pop a element from the deque 'deque' and set *data to this value. If data pointer is NULL, then the pop-ed value is discarded.

bool name_empty_p(const name_t deque)

Return true if the deque is empty, false otherwise.

void name_swap(name_t deque1, name_t deque2)

Swap the deque 'deque1' and 'deque2'.

void name_it(name_it_t it, name_t deque)

Set the iterator 'it' to the first element of 'deque' (aka the front). There is no destructor associated to this initialization.

void name_it_set(name_it_t it, const name_it_t ref)

Set the iterator 'it' to the iterator 'ref'. There is no destructor associated to this initialization.

bool name_end_p(const name_it_t it)

Return true if the iterator doesn't reference a valid element anymore.

bool name_last_p(const name_it_t it)

Return true if the iterator references the last element or if the iterator doesn't reference a valid element anymore.

bool name_it_equal_p(const name_it_t it1, const name_it_t it2)

Return true if the iterator it1 references the same element than it2.

void name_next(name_it_t it)

Move the iterator 'it' to the next element of the deque, ie. from the front element to the back element.

void name_previous(name_it_t it)

Move the iterator 'it' to the previous element of the deque, ie. from the back element to the front element.

type *name_ref(name_it_t it)

Return a pointer to the element pointed by the iterator. This pointer remains valid until the deque is modified by another method.

const type *name_cref(const name_it_t it)

Return a constant pointer to the element pointed by the iterator. This pointer remains valid until the deque is modified by another method.

type *name_get(const name_t deque, size_t i)

Return a pointer to the element i-th of the deque (from 0). It is assumed than i is within the size of the deque. The algorithm complexity is in O(ln(n))

const type *name_cget(const name_t deque, size_t i)

Return a constant pointer to the element i-th of the deque (from 0). It is assumed than i is within the size of the deque. The algorithm complexity is in O(ln(n))

size_t name_size(const name_t deque)

Return the number elements of the deque (aka size). Return 0 if there no element.

void name_get_str(string_t str, const name_t deque, bool append)

Generate a string representation of the deque 'deque' and set 'str' to this representation (if 'append' is false) or append 'str' with this representation (if 'append' is true). This method is only defined if the type of the element defines a GET_STR method itself.

bool name_parse_str(name_t deque, const char str[], const char **endp)

Parse the string 'str' that is assumed to be a string representation of a deque and set 'deque' to this representation. This method is only defined if the type of the element defines PARSE_STR & INIT methods itself. It returns true if success, false otherwise. If endp is not NULL, it sets '*endp' to the pointer of the first character not decoded by the function.

void name_out_str(FILE *file, const name_t deque)

Generate a string representation of the deque 'deque' and outputs it into the FILE 'file'. This method is only defined if the type of the element defines a OUT_STR method itself.

void name_in_str(name_t deque, FILE *file)

Read from the file 'file' a string representation of a deque and set 'deque' to this representation. This method is only defined if the type of the element defines a IN_STR method itself.

bool name_equal_p(const name_t deque1, const name_t deque2)

Return true if both deque 'deque1' and 'deque2' are equal. This method is only defined if the type of the element defines a EQUAL method itself.

size_t name_hash(const name_t deque)

Return the has value of 'deque'. This method is only defined if the type of the element defines a HASH method itself.

void name_swap_at(name_t deque, size_t i, size_t j)

Swap the values within the deque pointed by 'i' and by 'j'. 'i' & 'j' shall be valid index within the deque. This method is only defined if the type of the element defines a SWAP method itself.

M-DICT

A dictionary (or associative array, map, symbol table) is an abstract data type composed of a collection of (key, value) pairs, such that each possible key appears at most once in the collection.

DICT_DEF2(name, key_type[, key_oplist], value_type[, value_oplist])

Define the dictionary 'name##_t' and its associated methods as "static inline" functions. 'name' shall be a C identifier that will be used to identify the container. Current implementation uses chained Hash-Table and as such, elements in the dictionary are unordered.

It shall be done once per type and per compilation unit. It also define the iterator name##_it_t and its associated methods as "static inline" functions.

The object oplist is expected to have at least the following operators (INIT, INIT_SET, SET and CLEAR), otherwise default operators are used. If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object.

The key_oplist shall also define the additional operators (HASH and EQUAL).

Example:

DICT_DEF2(dict_str, string_t, STRING_OPLIST, string_t, STRING_OPLIST)
dict_str_t my_dict;
void f(string_t key, string_t value) {
	dict_str_set_at (my_dict, key, value);
}

DICT_STOREHASH_DEF2(name, key_type[, key_oplist], value_type[, value_oplist])

Define the dictionary 'name##_t' and its associated methods as "static inline" functions just like DICT_DEF2.

The only difference is that it stores the computed hash in the dictionary. This enable the container to avoid recomputing it in some occasions resulting in faster dictionary if the hash is costly to compute, or slower otherwise.

DICT_OA_DEF2(name, key_type[, key_oplist], value_type[, value_oplist])

Define the dictionary 'name##_t' and its associated methods as "static inline" functions much like DICT_DEF2. The difference is that it uses an Open Addressing Hash-Table as container.

It shall be done once per type and per compilation unit. It also define the iterator name##_it_t and its associated methods as "static inline" functions.

The object oplist is expected to have at least the following operators (INIT, INIT_SET, SET and CLEAR), otherwise default operators are used. If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object.

The key_oplist shall also define the additional operators : HASH and EQUAL and OOR_EQUAL and OOR_SET

This implementation is in general faster for small types of keys (like integer).

Example:

static inline bool oor_equal_p(int k, unsigned char n) {
  return k == (int)-n-1;
}
static inline void oor_set(int *k, unsigned char n) {
  *k = (int)-n-1;
}

DICT_OA_DEF2(dict_int, int, M_OPEXTEND(M_DEFAULT_OPLIST, OOR_EQUAL(oor_equal_p), OOR_SET(oor_set M_IPTR)), int64_t, M_DEFAULT_OPLIST)

dict_int_t my_dict;
void f(int key, int64_t value) {
	dict_int_set_at (my_dict, key, value);
}

DICT_OPLIST(name[, key_oplist, value_oplist])

Return the oplist of the dictionary defined by calling DICT_DEF2 with name & key_oplist & value_oplist.

DICT_SET_DEF(name, key_type[, key_oplist])

Define the set 'name##_t' and its associated methods as "static inline" functions. A set is a specialized version of a dictionary with no value.

It shall be done once per type and per compilation unit. It also define the iterator name##_it_t and its associated methods as "static inline" functions.

The object oplist is expected to have at least the following operators (INIT, INIT_SET, SET, CLEAR, HASH and EQUAL), otherwise default operators are used. If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object.

Example:

DICT_SET_DEF2(dict_strSet, string_t)
dict_strSet_t set;
void f(string_t key) {
	dict_strSet_set_at (set, key);
}

DICT_SET_OPLIST(name[, key_oplist])

Return the oplist of the set defined by calling DICT_SET_DEF2 with name & key_oplist.

Created methods

In the following methods, name stands for the name given to the macro that is used to identify the type. The following types are automatically defined by the previous macro:

name_t

Type of the dictionary of 'key_type' --> 'value_type'.

name_it_t

Type of an iterator over this dictionary.

The following methods are automatically and properly created by the previous macro:

void name_init(name_t dict)

Initialize the dictionary 'dict' to be empty.

void name_clear(name_t dict)

Clear the dictionary 'dict'.

void name_init_set(name_t dict, const name_t ref)

Initialize the dictionary 'dict' to be the same as 'ref'.

void name_set(name_t dict, const name_t ref)

Set the dictionary 'dict' to be the same as 'ref'.

void name_init_move(name_t dict, name_t ref)

Initialize the dictionary 'dict' by stealing as resource as possible from 'ref' and clear 'ref'.

void name_move(name_t dict, name_t ref)

Set the dictionary 'dict' by stealing as resource as possible from 'ref' and clear 'ref'.

void name_clean(name_t dict)

Clean the dictionary 'dict'. 'dict' remains initialized.

size_t name_size(const name_t dict)

Return the number of elements of the dictionary.

value_type *name_get(const name_t dict, const key_type key)

Return a pointer to the value associated to the key 'key' in dictionary 'dict' or NULL if the key is not found.

void name_set_at(name_t dict, const key_type key, const value_type value)

Set the value referenced by key 'key' in the dictionary 'dict' to 'value'. This method is only defined for associative containers (no SET).

void name_push(name_t dict, const key_type key)

Push the value referenced by key 'key' into the dictionary 'dict'. This method is only defined for SET.

void name_erase(name_t dict, const key_type key)

Remove the element referenced by key 'key' in the dictionary 'dict'. Do nothing if 'key' is no present in the dictionary.

void name_it(name_it_t it, name_t dict)

Set the iterator 'it' to the first element of 'dict'.

void name_it_set(name_it_t it, const name_it_t ref)

Set the iterator 'it' to the same element than 'ref'.

bool name_end_p(const name_it_t it)

Return true if 'it' references no longer a valid element.

bool name_last_p(const name_it_t it)

Return true if 'it' references the last element or is no longer valid.

void name_next(name_it_t it)

Update the iterator 'it' to the next element.

struct name_pair_s *name_ref(name_it_t it)

Return a pointer to the pair composed by the key ('key' field) and its value ('value' field). 'key' element can not be modified. This pointer remains valid until the dictionary is modified by another method.

const struct name_pair_s *name_ref(name_it_t it)

Return a constant pointer to the pair composed by the key('key' field) and its value ('value' field). This pointer remains valid until the dictionary is modified by another method.

void name_get_str(string_t str, const name_t dict, bool append)

Generate a string representation of the dict 'dict' and set 'str' to this representation (if 'append' is false) or append 'str' with this representation (if 'append' is true). This method is only defined if the type of the element defines a GET_STR method itself.

bool name_parse_str(name_t dict, const char str[], const char **endp)

Parse the string 'str' that is assumed to be a string representation of a dict and set 'dict' to this representation. This method is only defined if all types of the element defines PARSE_STR methods itself. It returns true if success, false otherwise. If endp is not NULL, it sets '*endp' to the pointer of the first character not decoded by the function.

void name_out_str(FILE *file, const name_t dict)

Generate a string representation of the dict 'dict' and outputs it into the FILE 'file'. This method is only defined if the type of the element defines a OUT_STR method itself.

void name_in_str(name_t dict, FILE *file)

Read from the file 'file' a string representation of a dict and set 'dict' to this representation. This method is only defined if the type of the element defines a IN_STR method itself.

bool name_equal_p(const name_t dict1, const name_t dict2)

Return true if both dict 'dict1' and 'dict2' are equal. This method is only defined if the type of the element defines a EQUAL method itself.

M-TUPLE

A tuple is a finite ordered list of elements of different types.

TUPLE_DEF2(name, (element1, type1[, oplist1]) [, ...])

Define the tuple 'name##_t' and its associated methods as "static inline" functions. Each parameter of the macro is expected to be an element of the tuple. Each element is defined by three parameters within parenthesis: the element name, the element type and the element oplist. 'name' and 'element' shall be a C identifier that will be used to identify the container.

This is more or less like a C structure. The main added value compared to using a struct is that it generates also all the basic methods to handle it. In fact, it generates a C struct with the given type and element.

It shall be done once per type and per compilation unit.

The object oplist is expected to have at least the following operators (INIT_SET, SET and CLEAR), otherwise default operators are used. If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object.

Example:

TUPLE_DEF2(pair, (key, string_t, STRING_OPLIST),
		 (value, mpz_t, (INIT(mpz_init), INIT_SET(mpz_init_set), SET(mpz_set), CLEAR(mpz_clear) )))
void f(void) {
	pair_t p1;
	pair_init (p1);
	string_set_str(p1->key, "HELLO");
	mpz_set_str(p1->value, "1742", 10);
	pair_clear(p1);
}

TUPLE_OPLIST(name, oplist1[, ...] )

Return the oplist of the tuple defined by calling TUPLE_DEF2 with the given name & the oplists.

Created methods

In the following methods, name stands for the name given to the macro that is used to identify the type. The following types are automatically defined by the previous macro:

name_t

Type of the defined tuple.

The following methods are automatically and properly created by the previous macros:

void name_init(name_t tuple)

Initialize the tuple 'tuple' (aka constructor) to an empty tuple. This method is defined if all methods define an INIT method.

void name_init_set(name_t tuple, const name_t ref)

Initialize the tuple 'tuple' (aka constructor) and set it to the value of 'ref'.

void name_init_set2(name_t tuple, const type1 element1[, ...])

Initialize the tuple 'tuple' (aka constructor) and set it to the value of the tuple ('element1'[, ...]).

void name_set(name_t tuple, const name_t ref)

Set the tuple 'tuple' to the value of 'ref'.

void name_init_move(name_t tuple, name_t ref)

Initialize the tuple 'tuple' (aka constructor) by stealing as many resources from 'ref' as possible. After-wise 'ref' is cleared. This method is created only if all oplists of the tuple define INIT_MOVE method.

void name_move(name_t tuple, name_t ref)

Set the tuple 'tuple' by stealing as many resources from 'ref' as possible. After-wise 'ref' is cleared. This method is created only if all oplists of the tuple define MOVE method.

void name_clear(name_t tuple)

Clear the tuple 'tuple (aka destructor).

const type1 *name_cget_at_element1(const name_t tuple)

Return a constant pointer to the element 'element1' of the tuple. There is as many methods as there are elements.

type1 *name_get_at_element1(const name_t tuple)

Return a pointer to the element 'element1' of the tuple. There is as many methods as there are elements.

void name_set_element1(name_t tuple, type1 element1)

Set the element of the tuple to 'element1' There is as many methods as there are elements.

int name_cmp(const name_t tuple1, const name_t tuple2)

Compare 'tuple1' to 'tuple2' using lexicographic order. This method is created only if all oplists of the tuple define CMP method.

int name_cmp_element1(const name_t tuple1, const name_t tuple2)

Compare 'tuple1' to 'tuple2' using only the element element1 as reference. This method is created only if the oplist of element1 defines the CMP method.

int name_equal_p(const name_t tuple1, const name_t tuple2)

Return true if 'tuple1' and 'tuple2' are identical. This method is created only if all oplists of the tuple define EQUAL method.

void name_get_str(string_t str, const name_t tuple, bool append)

Generate a string representation of the tuple 'tuple' and set 'str' to this representation (if 'append' is false) or append 'str' with this representation (if 'append' is true). This method is only defined if all oplists define a GET_STR method.

bool name_parse_str(name_t tuple, const char str[], const char **endp)

Parse the string 'str' that is assumed to be a string representation of a tuple and set 'tuple' to this representation. This method is only defined if all types of the element defines PARSE_STR & INIT methods itself. It returns true if success, false otherwise. If endp is not NULL, it sets '*endp' to the pointer of the first character not decoded by the function.

void name_out_str(FILE *file, const name_t tuple)

Generate a string representation of the tuple 'tuple' and outputs it into the FILE 'file'. This method is only defined if all oplists define a OUT_STR method.

void name_in_str(name_t tuple, FILE *file)

Read from the file 'file' a string representation of a tuple and set 'tuple' to this representation. This method is only defined if all oplists define a IN_STR method.

M-VARIANT

A variant is a finite exclusive list of elements of different types : the variant can be only equal to one element at a time.

VARIANT_DEF2(name, (element1, type1[, oplist1]) [, ...])

Define the variant 'name##_t' and its associated methods as "static inline" functions. Each parameter of the macro is expected to be an element of the variant. Each element is defined by three parameters within parenthesis: the element name, the element type and the element oplist. 'name' and 'element' shall be a C identifier that will be used to identify the container.

This is like a C union. The main added value compared to using a union is that it generates all the basic methods to handle it and it dynamically identifies which element is stored within.

It shall be done once per type and per compilation unit.

The object oplist is expected to have at least the following operators (INIT_SET, SET and CLEAR), otherwise default operators are used. If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object.

Example:

VARIANT_DEF2(either, (key, string_t, STRING_OPLIST),
		 (value, mpz_t, (INIT(mpz_init), INIT_SET(mpz_init_set), SET(mpz_set), CLEAR(mpz_clear) )))
void f(sting_t s) {
	either_t p1;
	either_init (p1);
	either_set_key(p1, s);
	either_clear(p1);
}

VARIANT_OPLIST(name, oplist1[, ...] )

Return the oplist of the variant defined by calling VARIANT_DEF2 with the given name & the oplists.

Created methods

In the following methods, name stands for the name given to the macro that is used to identify the type. The following types are automatically defined by the previous macro:

name_t

Type of the defined variant.

The following methods are automatically and properly created by the previous macros:

void name_init(name_t variant)

Initialize the variant 'variant' (aka constructor) to be empty.

void name_init_set(name_t variant, const name_t ref)

Initialize the variant 'variant' (aka constructor) and set it to the value of 'ref'.

void name_set(name_t variant, const name_t ref)

Set the variant 'variant' to the value of 'ref'.

void name_init_move(name_t variant, name_t ref)

Initialize the variant 'variant' (aka constructor) by stealing as many resources from 'ref' as possible. After-wise 'ref' is cleared. This method is created only if all oplists of the variant define INIT_MOVE method.

void name_move(name_t variant, name_t ref)
void name_move_elementN(name_t variant, typeN ref)

Set the variant 'variant' by stealing as many resources from 'ref' as possible. After-wise 'ref' is cleared. This method is created only if all oplists of the variant define MOVE method.

void name_clear(name_t variant)

Clear the variant 'variant (aka destructor).

void name_clean(name_t variant)

Clean the variant 'variant and make it empty.

void name_init_elementN(name_t variant)

Initialize the variant 'variant' to the type of 'element1' This method is defined if all methods define an INIT method.

void name_init_set_elementN(name_t variant, const typeN elementN)

Initialize and set the variant 'variant' to the type and value of 'elementN'.

void name_set_elementN(name_t variant, const typeN elementN)

Set the variant 'variant' to the type and value of 'elementN'.

const typeN * name_cget_at_elementN(name_t variant)

Return a pointer to the 'variant' viewed as of type 'typeN'. If the variant isn't an object of such type, it returns NULL.

typeN * name_get_at_elementN(name_t variant)

Return a pointer to the 'variant' viewed as of type 'typeN'. If the variant isn't an object of such type, it returns NULL.

bool name_empty_p(const name_t variant)

Return true if the variant is empty, false otherwise.

bool name_elementN_p(const name_t variant)

Return true if the variant is of the type of 'elementN'.

size_t name_hash(const name_t variant)

Return a hash associated to the variant. All types associated to the variant shall have a hash function for this function to be defined.

bool name_equal_p(const name_t variant1, const name_t varian2)

Return true if both objects are equal, false otherwise. All types associated to the variant shall have a equal_p function for this function to be defined.

void name_swap(name_t variant1, name_t varian2)

Swap both objects.

void name_get_str(string_t str, name_t variant, bool append)

Convert the variant into a string, appending it into 'str' or not. All types associated to the variant shall have a GET_STR method for this function to be defined.

bool name_parse_str(name_t variant, const char str[], const char **endp)

Parse the string 'str' that is assumed to be a string representation of a variant and set 'variant' to this representation. This method is only defined if all types of the element defines PARSE_STR & INIT methods itself. It returns true if success, false otherwise. If endp is not NULL, it sets '*endp' to the pointer of the first character not decoded by the function.

void name_out_str(FILE *file, name_t variant)

Convert the variant into a string and send it to the stream 'file'. All types associated to the variant shall have a out_str function for this function to be defined.

void name_in_str(name_t variant, FILE *file)

Read a string representation of the variant from the stream 'file' and update the object variant with it. All types associated to the variant shall have a in_str function for this function to be defined. This method is defined if all methods define an INIT method.

M-RBTREE

RBTREE_DEF(name, type[, oplist])

Define the binary ordered tree 'name##_t' and its associated methods as "static inline" functions. A binary tree is a tree data structure in which each node has at most two children, which are referred to as the left child and the right child. In this kind of tree, all elements of the tree are totally ordered. The current implementation is RED-BLACK TREE. It shall not be confused with a B-TREE. 'name' shall be a C identifier that will be used to identify the container.

The CMP operator is used to perform the total ordering of the elements.

The UPDATE operator is used to update an element if the pushed item already exist in the container. The default behavior will overwrite the recorded value with the new one.

It shall be done once per type and per compilation unit. It also define the iterator name##_it_t and its associated methods as "static inline" functions.

The object oplist is expected to have at least the following operators (INIT, INIT_SET, SET, CLEAR and CMP), otherwise default operators are used. If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object.

Example:

RBTREE_DEF(rbtree_uint, unsigned int)
void f(unsigned int num) {
	rbtree_uint_t tree;
	rbtree_uint_init(tree);
	for(unsigned int i = 0; i < num; i++)
		rbtree_uint_push(tree, i);
	rbtree_uint_clear(tree);                              
}

RBTREE_OPLIST(name [, oplist])

Return the oplist of the Red-Black defined by calling RBTREE_DEF with name & oplist. If there is no given oplist, the default oplist for standard C type is used.

Created methods

The following methods are automatically and properly created by the previous macros. In the following methods, name stands for the name given to the macro that is used to identify the type.

name_t

Type of the Red Black Tree of 'type'.

name_it__t

Type of an iterator over this Red Black Tree.

void name_init(name_t rbtree)

Initialize the Red Black Tree 'rbtree' to be empty.

void name_clear(name_t rbtree)

Clear the Red Black Tree 'rbtree'.

void name_init_set(name_t rbtree, const name_t ref)

Initialize the Red Black Tree 'rbtree' to be the same as 'ref'.

void name_set(name_t rbtree, const name_t ref)

Set the Red Black Tree 'rbtree' to be the same as 'ref'.

void name_init_move(name_t rbtree, name_t ref)

Initialize the Red Black Tree 'rbtree' by stealing as resource as possible from 'ref' and clear 'ref'.

void name_move(name_t rbtree, name_t ref)

Set the Red Black Tree 'rbtree' by stealing as resource as possible from 'ref' and clear 'ref'.

void name_clean(name_t rbtree)

Clean the Red Black Tree 'rbtree'. 'rbtree' remains initialized but empty.

size_t name_size(const name_t rbtree)

Return the number of elements of the Red Black Tree.

void name_push(name_t rbtree, const type data)

Push 'data' into the Red Black Tree 'rbtree' at its ordered place while keeping the tree balanced. If the UPDATE operator is defined and there exists a node that equals (CMP) 'data' it will be used to update the data of the node on push (It can be used to increment value). Otherwise the value is overwritten.

void name_pop(type *dest, name_t rbtree, const type data)

Pop 'data' from the Red Black Tree 'rbtree' and save the popped value into 'dest' if the pointer is not null while keeping the tree balanced. Do nothing if 'data' is no present in the Red Black Tree.

type * name_min(const name_t rbtree)
const type * name_cmin(const name_t rbtree)

Return a pointer to the minimum element of the tree or NULL if there is no element.

type * name_max(const name_t rbtree)
const type * name_cmax(const name_t rbtree)

Return a pointer to the maximum element of the tree or NULL if there is no element.

type * name_get(const name_t rbtree, const type *data)
const type * name_cget(const name_t rbtree, const type *data)

Return a pointer to the element of the tree 'rbtree' that is equal to 'data', or NULL if there is no match.

void name_swap(name_t rbtree1, name_t rbtree2)

Swap both trees.

bool name_empty_p(const name_t rbtree)

Return true if the tree is empty, false otherwise.

void name_it(name\it__t it, name_t rbtree)

Set the iterator 'it' to the first element of 'rbtree'.

void name_it_set(name\it__t it, const name\it__t ref)

Set the iterator 'it' to the same element than 'ref'.

void name_it_last(name\it__t it, name_t rbtree)

Set the iterator 'it' to the last element of 'rbtree'.

void name_it_end(name\it__t it, name_t rbtree)

Set the iterator 'it' to no element of 'rbtree'.

void name_it_from(name\it__t it, const name_t rbtree, const type data)

Set the iterator 'it' to the greatest element of 'rbtree' lower of equal than 'data' or the first element is there is none.

bool name_end_p(const name\it__t it)

Return true if 'it' references no longer a valid element.

bool name_last_p(const name\it__t it)

Return true if 'it' references the last element or is no longer valid.

bool name_to_p(const name\it__t it, const type data)

Return true if 'it' references an element that is greater or equal than 'data'.

void name_next(name\it__t it)

Update the iterator 'it' to the next element.

void name_previous(name\it__t it)

Update the iterator 'it' to the previous element.

type *name_ref(name\it__t it)
const type *name_ref(name\it__t it)

Return a pointer to the element pointer by the iterator 'it'. This pointer remains valid until the Red Black Tree is modified by another method.

void name_get_str(string_t str, const name_t rbtree, bool append)

Generate a string representation of the rbtree 'rbtree' and set 'str' to this representation (if 'append' is false) or append 'str' with this representation (if 'append' is true). This method is only defined if the type of the element defines a GET_STR method itself.

bool name_parse_str(name_t tree, const char str[], const char **endp)

Parse the string 'str' that is assumed to be a string representation of a RBTREE and set 'tree' to this representation. This method is only defined if all types of the element defines PARSE_STR & INIT methods itself. It returns true if success, false otherwise. If endp is not NULL, it sets '*endp' to the pointer of the first character not decoded by the function.

void name_out_str(FILE *file, const name_t rbtree)

Generate a string representation of the rbtree 'rbtree' and outputs it into the FILE 'file'. This method is only defined if the type of the element defines a OUT_STR method itself.

void name_in_str(name_t rbtree, FILE *file)

Read from the file 'file' a string representation of a rbtree and set 'rbtree' to this representation. This method is only defined if the type of the element defines a IN_STR method itself.

bool name_equal_p(const name_t rbtree1, const name_t rbtree2)

Return true if both rbtree 'rbtree1' and 'rbtree2' are equal. This method is only defined if the type of the element defines a EQUAL method itself.

size_t name_hash_p(const name_t rbtree)

Return the hash of the tree. This method is only defined if the type of the element defines a HASH method itself.

M-BPTREE

A B+TREE is a variant of BTREE that itself is a generalization of Binary Tree.

A B+TREE is an N-ary tree with a variable but often large number of children per node. It is mostly used for handling slow media by file system and database.

It provides a fully sorted container allowing fast access to individual item or range of items, and as such is concurrent to Red-Black Tree. On modern architecture, a B+TREE is typically faster than a red-black tree due to being more cache friendly (The RAM itself can be considered as a slow media nowadays!)

When defining a B+TREE it is necessary to give the type of the item within, but also the maximum number of child per node. The best maximum number of child per node depends on the type itself (its size, its compare cost) and the cache of the processor.

BPTREE_DEF2(name, N, key_type, key_oplist, value, value_oplist)

Define the B+TREE tree of rank N 'name##_t' and its associated methods as "static inline" functions. This B+TREE will be created as an associative array of the key_type to the value_type.

The CMP operator is used to perform the total ordering of the key elements.

It shall be done once per type and per compilation unit. It also define the iterator name##_it_t and its associated methods as "static inline" functions.

The object oplist is expected to have at least the following operators (INIT, INIT_SET, SET, CLEAR and CMP), otherwise default operators are used. If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object.

Example:

BPTREE_DEF2(tree_uint, unsigned int, (), float, ())
void f(unsigned int num) {
	tree_uint_t tree;
	tree_uint_init(tree);
	for(unsigned int i = 0; i < num; i++)
		tree_uint_set_at(tree, i, (float) i);
	tree_uint_clear(tree);
}

BPTREE_OPLIST2(name, key_oplist, value_oplist)

Return the oplist of the BPTREE defined by calling BPTREE_DEF2 with name, key_oplist and value_oplist.

BPTREE_DEF(name, N, key_type[, key_oplist])

Define the B+TREE tree of rank N 'name##_t' and its associated methods as "static inline" functions. This B+TREE will be created as an ordered set of key_type.

The CMP operator is used to perform the total ordering of the key elements.

It shall be done once per type and per compilation unit. It also define the iterator name##_it_t and its associated methods as "static inline" functions.

The object oplist is expected to have at least the following operators (INIT, INIT_SET, SET, CLEAR and CMP), otherwise default operators are used. If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object.

In the following specification, in this case, value_type will be defined as the same as key_type.

Example:

BPTREE_DEF(tree_uint, unsigned int)
void f(unsigned int num) {
	tree_uint_t tree;
	tree_uint_init(tree);
	for(unsigned int i = 0; i < num; i++)
		tree_uint_push(tree, i);
	tree_uint_clear(tree);
}

BPTREE_OPLIST(name[, key_oplist])

Return the oplist of the BPTREE defined by calling BPTREE_DEF with name, key_oplist. If there is no given oplist, the default oplist for standard C type is used.

Created methods

The following methods are automatically and properly created by the previous macros. In the following methods, name stands for the name given to the macro that is used to identify the type.

name_t

Type of the B+Tree of 'type'.

name_it__t

Type of an iterator over this B+Tree.

void name_init(name_t tree)

Initialize the B+Tree 'tree' and set it to empty.

void name_clear(name_t tree)

Clear the B+Tree 'tree'.

void name_init_set(name_t tree, const name_t ref)

Initialize the B+Tree 'tree' to be the same as 'ref'.

void name_set(name_t tree, const name_t ref)

Set the B+Tree 'tree' to be the same as 'ref'.

void name_init_move(name_t tree, name_t ref)

Initialize the B+Tree 'tree' by stealing as resource as possible from 'ref' and clear 'ref'.

void name_move(name_t tree, name_t ref)

Set the B+Tree 'tree' by stealing as resource as possible from 'ref' and clear 'ref'.

void name_clean(name_t tree)

Clean the B+Tree 'tree'. 'tree' remains initialized but empty.

size_t name_size(const name_t tree)

Return the number of elements of the B+Tree.

void name_push(name_t tree, const key_type data)

Push 'data' into the B+Tree 'tree' at the right order while keeping the tree balanced. This function is defined only if the tree is not defined as an associative array.

void name_set_at(name_t tree, const key_type data, const value_type val)

Associate the value 'val' to the key 'data' in the B+Tree 'tree' while keeping the tree balanced. This function is defined only if the tree is defined as an associative array.

void name_pop(value_type *dest, name_t tree, const key_type data)

Pop 'data' from the B+Tree 'tree' and save the popped value into 'dest' if the pointer is not null while keeping the tree balanced. Do nothing if 'data' is no present in the B+Tree.

bool name_erase(name_t tree, const key_type data)

Remove 'data' from the B+Tree 'tree' while keeping the tree balanced. Return true if the data is removed, false if nothing is done (data is not present).

value_type * name_min(const name_t tree)
const value_type * name_cmin(const name_t tree)

Return a pointer to the minimum element of the tree or NULL if there is no element.

value_type * name_max(const name_t tree)
const value_type * name_cmax(const name_t tree)

Return a pointer to the maximum element of the tree or NULL if there is no element.

value_type * name_get(const name_t tree, const key_type *data)
const value_type * name_cget(const name_t tree, const key_type *data)

Return a pointer to the value of the tree 'tree' that is associated to 'data', or NULL if there is no match.

void name_swap(name_t tree1, name_t tree2)

Swap both trees.

bool name_empty_p(const name_t tree)

Return true if the tree is empty, false otherwise.

void name_it(name_it__t it, name_t tree)

Set the iterator 'it' to the first element of 'tree'.

void name_it_set(name_it__t it, const name_it__t ref)

Set the iterator 'it' to the same element than 'ref'.

void name_it_end(name_it__t it, name_t tree)

Set the iterator 'it' to no element of 'tree'.

void name_it_from(name_it__t it, const name_t tree, const type data)

Set the iterator 'it' to the greatest element of 'tree' lower of equal than 'data' or the first element is there is none.

bool name_end_p(const name_it__t it)

Return true if 'it' references no longer a valid element.

bool name_to_p(const name_it__t it, const type data)

Return true if 'it' references an element that is greater or equal than 'data'.

bool name_it_equal_p(const name_it__t it1, const name_it__t it1)

Return true if both iterators reference the same object.

void name_next(name_it__t it)

Update the iterator 'it' to the next element.

type *name_ref(name_it__t it)
const type *name_ref(name_it__t it)

Return a pointer to the element pointer by the iterator 'it'. This pointer remains valid until the B+Tree is modified by another method.

void name_get_str(string_t str, const name_t tree, bool append)

Generate a string representation of the tree 'tree' and set 'str' to this representation (if 'append' is false) or append 'str' with this representation (if 'append' is true). This method is only defined if the type of the element defines a GET_STR method itself.

bool name_parse_str(name_t tree, const char str[], const char **endp)

Parse the string 'str' that is assumed to be a string representation of a tree and set 'tree' to this representation. This method is only defined if the type of the element defines a PARSE_STR method itself. It returns true if success, false otherwise. If endp is not NULL, it sets '*endp' to the pointer of the first character not decoded by the function.

void name_out_str(FILE *file, const name_t tree)

Generate a string representation of the tree 'tree' and outputs it into the FILE 'file'. This method is only defined if the type of the element defines a OUT_STR method itself.

void name_in_str(name_t tree, FILE *file)

Read from the file 'file' a string representation of a tree and set 'tree' to this representation. This method is only defined if the type of the element defines a IN_STR method itself.

bool name_equal_p(const name_t tree1, const name_t tree2)

Return true if both trees 'tree1' and 'tree2' are equal. This method is only defined if the type of the element defines a EQUAL method itself.

size_t name_hash_p(const name_t tree)

Return the hash of the tree. This method is only defined if the type of the element defines a HASH method itself.

M-PRIOQUEUE

A priority queue is a queue where each element has a "priority" associated with it: an element with high priority is served before an element with low priority. It is currently implemented as a heap.

PRIOQUEUE_DEF(name, type [, oplist])

Define the priority queue 'name##_t' and its associated methods as "static inline" functions. The queue will be composed of object of type 'type'.

'name' shall be a C identifier that will be used to identify the container.

The CMP operator is used to sort the queue so that it always returns the minimum of the queue. The EQUAL operator is used to identify an item on UPDATE or REMOVE operations. It is uncorrelated with the CMP operator from the point of view of this operator.

The object oplist is expected to have at least the following operators (INIT, INIT_SET, SET, CLEAR, CMP and EQUAL), otherwise default operators are used. If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object.

PRIOQUEUE_OPLIST(name, [, oplist])

Define the oplist of the prioqueue defined with 'name' and potentially 'oplist'. If there is no given oplist, the default oplist for standard C type is used.

Created methods

The following methods are automatically and properly created by the previous macros. In the following methods, name stands for the name given to the macro that is used to identify the type.

name_t

Type of the priority queue of 'type'.

name_it__t

Type of an iterator over this priority queue.

void name_init(name_t queue)

Initialize the priority queue 'queue' and set it to empty.

void name_clear(name_t queue)

Clear the priority queue 'tree'.

void name_init_set(name_t queue, const name_t ref)

Initialize the Priority Queue 'queue' to be the same as 'ref'.

void name_set(name_t queue, const name_t ref)

Set the Priority Queue 'queue' to be the same as 'ref'.

void name_init_move(name_t queue, name_t ref)

Initialize the Priority Queue 'queue' by stealing as resource as possible from 'ref' and clear 'ref'.

void name_move(name_t queue, name_t ref)

Set the Priority Queue 'queue' by stealing as resource as possible from 'ref' and clear 'ref'.

void name_clean(name_t queue)

Clean the Priority Queue 'queue'. 'queue' remains initialized but empty.

size_t name_size(const name_t queue)

Return the number of elements of the Priority Queue.

bool name_empty_p(const name_t queue)

Return true if the queue is empty, false otherwise.

void name_swap(name_t queue1, name_t queue2)

Swap both queues.

void name_push(name_t queue, const type x)

Push 'x' into the Priority Queue 'queue' (somewhere in the queue).

const type *name_front(name_t queue)

Return a constant pointer to the item having the minimum value of all elements in the queue 'queue'.

void name_pop(type *dest, name_t queue)

Pop the minimum value from the priority Queue 'queue' and save the popped value into 'dest' if the pointer is not null.

void name_it(name_it__t it, name_t queue)

Set the iterator 'it' to the first element of 'queue'. It won't iterate from minimum to maximum but in an implementation define way that ensures that all items are accessed.

void name_it_last(name_it__t it, name_t queue)

Set the iterator 'it' to the last element of 'queue'.

void name_it_set(name_it__t it, const name_it__t ref)

Set the iterator 'it' to the same element than 'ref'.

void name_it_end(name_it__t it, name_t queue)

Set the iterator 'it' to no element of 'queue'.

bool name_end_p(const name_it__t it)

Return true if 'it' references no longer a valid element.

bool name_last_p(const name_it__t it)

Return true if 'it' references the last element, or there is no longer any valid element.

bool name_it_equal_p(const name_it__t it1, const name_it__t it2)

Return true if both iterators reference the same entries.

void name_next(name_it__t it)

Update the iterator 'it' to the next element.

void name_previous(name_it__t it)

Update the iterator 'it' to the previous element.

const type *name_cref(name_it__t it)

Return a constant pointer to the referenced item.

M-BUFFER

A circular buffer (or ring buffer) is a data structure using a single, bounded buffer as if it were connected end-to-end.

BUFFER_DEF(name, type, size, policy[, oplist])

Define the buffer 'name##_t' and its associated methods as "static inline" functions. A buffer is a fixed size queue or stack. If it is built with the BUFFER_THREAD_SAFE option (default) it can be used to transfer message from multiple producer threads to multiple consumer threads. This is done internally using a mutex and conditional waits.

'name' shall be a C identifier that will be used to identify the container.

The size parameter defined the fixed size of the queue. It can be 0, in which case, the fixed size will be defined at initialization time and the needed objects to handle the buffer will be allocated at initialization time too. Otherwise the needed objects will be embedded within the structure, preventing any other allocations.

Multiple additional policy can be applied to the buffer by performing a logical or of the following properties:

  • BUFFER_QUEUE : define a FIFO queue (default),
  • BUFFER_STACK : define a stack (exclusive with BUFFER_QUEUE),
  • BUFFER_BLOCKING : define blocking calls for the default push/pop methods (default); this is a synonym to BUFFER_BLOCKING_PUSH|BUFFER_BLOCKING_POP,
  • BUFFER_UNBLOCKING : define unblocking calls for the default push/pop methods; this is a synonym to BUFFER_UNBLOCKING_PUSH|BUFFER_UNBLOCKING_POP,
  • BUFFER_BLOCKING_PUSH : define blocking calls for the default push methods (default),
  • BUFFER_UNBLOCKING_PUSH : define unblocking calls for the default push methods,
  • BUFFER_BLOCKING_POP : define blocking calls for the default pop methods (default),
  • BUFFER_UNBLOCKING_POP : define unblocking calls for the default pop methods,
  • BUFFER_THREAD_SAFE : define thread safe functions (default),
  • BUFFER_THREAD_UNSAFE : define thread unsafe functions,
  • BUFFER_PUSH_INIT_POP_MOVE : change the behavior of PUSH to push a new initialized object, and POP as moving this new object into the new emplacement (this is mostly used for performance reasons or to handle properly a shared_ptr semantic). In practice, it works as if POP performs the initialization of the object.
  • BUFFER_PUSH_OVERWRITE : PUSH will always overwrite the first entry (this is mostly used to reduce latency).
  • BUFFER_DEFERRED_POP : do not consider the object to be fully popped from the buffer by calling the pop method until the call to pop_deferred ; this allows to handle object that are in-progress of being consumed by the thread.

This container is designed to be used for easy synchronization inter-threads (and the variable shall be a global shared one).

It shall be done once per type and per compilation unit.

The object oplist is expected to have at least the following operators (INIT, INIT_SET, SET and CLEAR), otherwise default operators are used. If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object. It supports also INIT_MOVE if available.

Example:

BUFFER_DEF(buffer_uint, unsigned int, 10, BUFFER_QUEUE|BUFFER_BLOCKING)

buffer_uint_t g_buff;

void f(unsigned int i) {
	buffer_uint_init(g_buff, 10);
	buffer_uint_push(g_buff, i);
	buffer_uint_pop(&i, g_buff);
	buffer_uint_clear(g_buff);
}

Created methods

The following methods are automatically and properly created by the previous macros. In the following methods, name stands for the name given to the macro that is used to identify the type.

void name_init(buffer_t buffer, size_t size)

Initialize the buffer 'buffer' to fill in at least 'size-1' elements. The 'size' argument shall be the same as the one used to create the buffer or the one used to create the buffer was '0'. This function is not thread safe.

void name_clear(buffer_t buffer)

Clear the buffer and destroy all its allocations. This function is not thread safe.

void name_clean(buffer_t buffer)

Clean the buffer making it empty but remain initialized. This function is thread safe if the buffer was built thread safe.

bool name_empty_p(const buffer_t buffer)

Return true if the buffer is empty, false otherwise. This function is thread safe if the buffer was built thread safe.

bool name_full_p(const buffer_t buffer)

Return true if the buffer is full, false otherwise. This function is thread safe if the buffer was built thread safe.

size_t name_size(const buffer_t buffer)

Return the number of elements in the buffer that can be en-queued. This function is thread safe if the buffer was built thread safe.

size_t name_overwrite(const buffer_t buffer)

If the buffer is built with the BUFFER_PUSH_OVERWRITE option, this function returns the number of elements that have been overwritten and thus discarded. If the buffer was not built with the BUFFER_PUSH_OVERWRITE option, it returns 0.

bool name_push(buffer_t buffer, const type data)

Push the object 'data' in the buffer 'buffer'. It waits for any empty room to come if the buffer was built as blocking. Returns true if the data was pushed, false otherwise. Always return true if the buffer is blocking. This function is thread safe if the buffer was built thread safe.

bool name_pop(type *data, buffer_t buffer)

Pop from the buffer 'buffer' into the object pointed by 'data'. It waits for any data to come if the buffer was built as blocking. If the buffer is built with the BUFFER_PUSH_INIT_POP_MOVE option, the object pointed by 'data' shall be uninitialized (the pop function will perform a quick initialization of the object using an INIT_MOVE operator) , otherwise it shall be an initialized object (the pop function will perform a SET operator). Returns true if a data was popped, false otherwise. Always return true if the buffer is blocking. This function is thread safe if the buffer was built thread safe.

bool name_push_blocking(buffer_t buffer, const type data, bool blocking)

Same as name_push except that the blocking policy is decided by the 'blocking' parameter.

bool name_pop_blocking(buffer_t buffer, const type data, bool blocking)

Same as name_pop except that the blocking policy is decided by the 'blocking' parameter.

TODO: Describe QUEUE_MPMC_DEF

TODO: Describe QUEUE_SPSC_DEF

M-SNAPSHOT

This header is for created snapshots.

A snapshot is a mechanism allowing a reader thread and a writer thread, working at different speeds, to exchange messages in a fast, reliable and thread safe way (the message is always passed automatically from a thread point of view) without waiting for synchronization. The consumer thread has only access to the latest published data of the producer thread. This is implemented in a fast way as the writer directly writes the message in the buffer that will be passed to the reader (avoiding copy of the buffer) and a simple exchange of index is sufficient to handle the switch.

This container is designed to be used for easy synchronization inter-threads (and the variable shall be a global shared one).

This is linked to shared atomic register in the literature and snapshot names vector of such registers where each element of the vector can be updated separately. They can be classified according to the number of producers/consumers: SPSC (Single Producer, Single Consumer), MPSC (Multiple Producer, Single Consumer), SPMC (Single Producer, Multiple Consumer), MPMC (Multiple Producer, Multiple Consumer),

The provided containers by the library are designed to handle huge structure efficiently and as such deal with the memory reclamation needed to handle them. If the data you are sharing are supported by the atomic header (like bool or integer), using atomic_load and atomic_store is a much more efficient and simple way to do even in the case of MPMC.

SNAPSHOT_SPSC_DEF(name, type[, oplist])

Define the snapshot 'name##_t' and its associated methods as "static inline" functions. Only a single reader thread and a single writer thread are supported. It is a SPSC atomic shared register. In practice, it is implemented using a triple buffer (lock-free).

It shall be done once per type and per compilation unit. Not all functions are thread safe.

The object oplist is expected to have at least the following operators (INIT, INIT_SET, SET and CLEAR), otherwise default operators are used. If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object. It supports also INIT_MOVE if available.

Example:

SNAPSHOT_DEF(snapshot_uint, unsigned int)
snapshot_uint_t message;
void f(unsigned int i) {
	unsigned *p = snapshot_uint_get_write_buffer(message);
	*p = i;
            snapshot_uint_write(message);
}
unsigned int g(void) {
	unsigned *p = snapshot_uint_read(message);
	return *p;
}

Created methods

The following methods are automatically and properly created by the previous macros. In the following methods, name stands for the name given to the macro that is used to identify the type.

void name_init(snapshot_t snapshot)

Initialize the snapshot 'snapshot'. This function is not thread safe.

void name_clear(snapshot_t snapshot)

Clear the snapshot and destroy all its allocations. This function is not thread safe.

void name_init_set(snapshot_t snapshot, const snapshot_t org)

Initialize the snapshot 'snapshot' from the state of 'org'. This function is not thread safe.

void name_set(snapshot_t snapshot, const snapshot_t org)

Set the snapshot 'snapshot' from the state of 'org'. This function is not thread safe.

void name_init_move(snapshot_t snapshot, snapshot_t org)

Move the contain of the snapshot 'org' to the uninitialized 'snapshot', clearing 'org' in the process. This function is not thread safe. This function is defined only if the underlying type has defined the INIT_MOVE operator.

void name_move(snapshot_t snapshot, snapshot_t org)

Move the contain of the snapshot 'org' to the initialized 'snapshot', clearing 'org' in the process. This function is not thread safe. This function is defined only if the underlying type has defined the MOVE operator.

type *name_write(snapshot_t snap)

Publish the 'in-progress' data of the snapshot 'snap so that the read thread can have access to the data. It returns the pointer to the new 'in-progress' data buffer of the snapshot (which is not yet published but will be published for the next call of name_write). This function is thread-safe and performs atomic operation on the snapshot.

type *name_read(snapshot_t snap)

Get access to the last published data of the snapshot 'snap'. It returns the pointer to the data. If a publication has been performed since the last call to name_read a new pointer to the data is returned. Otherwise the previous pointer is returned. This function is thread-safe and performs atomic operation on the snapshot.

bool name_updated_p(snapshot_t snap)

Return true if the buffer has updated data compared to the last time it was read. This function is thread-safe and performs atomic operation on the snapshot.

type *name_get_write_buffer(snapshot_t snap)

Return a pointer to the active 'in-progress' data of the snapshot 'snap'. It is the same as the last return from name_write. This function is thread-safe and performs atomic operation on the snapshot.

type *name_get_read_buffer(snapshot_t snap)

Return a pointer to the active published data of the snapshot 'snap'. It is the same as the last return from name_read. It doesn't perform any switch of the data that has to be read. This function is thread-safe and performs atomic operation on the snapshot.

TODO: Document SPMC & MPMC snapshots

M-SHARED

This header is for creating shared pointer. A shared pointer is a smart pointer that retains shared ownership of an object. Several shared pointers may own the same object, sharing ownership of an object.

SHARED_PTR_DEF(name, type[, oplist])

Define the shared pointer 'name##_t' and its associated methods as "static inline" functions. A shared pointer is a mechanism to keep tracks of all users of an object and performs an automatic destruction of the object whenever all users release their need on this object.

The tracking of ownership is atomic and the destruction of the object is thread safe.

The object oplist is expected to have at least the following operators (CLEAR and DEL), otherwise default operators are used. If there is no given oplist, the default oplist for standard C type is used or a globaly registered oplist is used. The created methods will use the operators to init, set and clear the contained object. It supports also INIT_MOVE if available.

There are designed to work with buffers with policy BUFFER_PUSH_INIT_POP_MOVE to send a shared pointer across multiple threads.

Example:

SHARED_PTR_DEF(shared_mpz, mpz_t, (CLEAR(mpz_clear)))
void f(void) {
	shared_mpz_t p;
	mpz_t z;
	mpz_init(z);
	shared_mpz_init2 (p, z);
	buffer_uint_push(g_buff1, p);
	buffer_uint_push(g_buff2, p);
	shared_mpz_clear(p);
}

Created methods

The following methods are automatically and properly created by the previous macros. In the following methods, name stands for the name given to the macro that is used to identify the type.

void name_init(shared_t shared)

Initialize the shared pointer 'shared' to NULL (no object is pointed). This function is not thread safe.

void name_init2(shared_t shared, type *data)

Initialize the shared pointer 'shared' to 'data'. User code shall not use 'data' anymore. This function is not thread safe.

void name_init_new(shared_t shared)

Initialize the shared pointer 'shared' to a new object of type 'type'. The default constructor of type is used to initialize the object. This function is not thread safe.

void name_init_set(shared_t shared, const shared_t src)

Initialize the shared pointer 'shared' to the same object than the one pointed by 'src'. This function is thread safe from 'src' point of view.

bool name_NULL_p(const shared_t shared)

Return true if shared doesn't point to any object.

void name_clear(shared_t shared)

Clear the shared pointer, destroying the shared object if no longer any other shared pointers point to the object. This function is thread safe.

void name_clean(shared_t shared)

Make the shared pointer points to no object any-longer, destroying the shared object if no longer any shared pointers point to the object. This function is thread safe.

void name_set(shared_t shared, const shared_t src)

Destroy the shared object pointed by 'shared' if no longer any other shared pointers point to it, set the shared pointer 'shared' to the same object than the one pointed by 'src'. This function is thread safe.

void name_init_move(shared_t shared, shared_t src)

Move the shared pointer from the initialized 'src' to 'shared'.

void name_init_move(shared_t shared, shared_t src)

Move the shared pointer from the initialized 'src' to 'shared', clearing first the shared object pointed by 'src' if needed.

void name_swap(shared_t shared1, shared_t shared2)

Swap the shared pointer.

bool name_equal_p(const shared_t shared1, const shared_t shared2)

Return true if both shared pointers point to the same object.

const type *name_cref(const shared_t shared)

Return a constant pointer to the shared object pointed by the shared pointer. Keeping the pointer whereas the shared pointer is destroyed is undefined behavior.

type *name_ref(const shared_t shared)

Return a pointer to the shared object pointed by the shared pointer. Keeping the pointer whereas the shared pointer is destroyed is undefined behavior.

TODO: Document shared resource

M-I-SHARED

This header is for creating intrusive shared pointer.

ISHARED_INTERFACE(name, type)

Extend the definition of the structure of an object of type 'type' by adding the necessary interface to handle it as a shared pointer named 'name'. It shall be put within the structure definition of the object (See example).

ISHARED_PTR_DEF(name, type[, oplist])

Define the associated methods to handle the shared pointer named 'name' as "static inline" functions. A shared pointer is a mechanism to keep tracks of all users of an object and performs an automatic destruction of the object whenever all users release their need on this object.

The destruction of the object is thread safe and to occur when all users of the object release it. The last user that releases it is the one that performs the destruction of the object. The destruction of the object implies:

  • calling the CLEAR operator to clear the object,
  • calling the DEL operator to release the memory used by the object (if the method has not been disabled).

The object oplist is expected to have the following operators (CLEAR and DEL), otherwise default operators are used. If there is no given oplist, the default operators are also used. The created methods will use the operators to init, set and clear the contained object.

There are designed to work with buffers with policy BUFFER_PUSH_INIT_POP_MOVE to send a shared pointer across multiple threads.

It is recommended to use the intrusive shared pointer over the standard one if possible (They are faster & cleaner).

Example:

    typedef struct mystruct_s {
            ISHARED_PTR_INTERFACE(ishared_mystruct, struct mystruct_s);
            char *message;
    } mystruct_t;

    static inline void mystruct_clear(mystruct_t *p) { free(p->message); }

    ISHARED_PTR_DEF(ishared_mystruct, mystruct_t, (CLEAR(mystruct_clear M_IPTR)))

    void f(void) {
            mystruct_t *p = ishared_mystruct_new();
            p->message = strdup ("Hello");
            buffer_mystruct_push(g_buff1, p);
            buffer_mystruct_push(g_buff2, p);
            ishared_mystruct_clear(p);
    }

Created methods

The following methods are automatically and properly created by the previous macros. In the following methods, name stands for the name given to the macro that is used to identify the type.

typedef type *name_t

It will define name_t as a pointer to shared counted object. This is a synonymous to a pointer to the object.

name_t name_init(type *object)

Return a shared pointer to 'object' with one user counter. The shared pointer part of 'object' shall not have been initialized. This function is not thread safe.

name_t name_init_set(name_t shared)

Return a new shared pointer to the same object than the one pointed by 'shared', incrementing the user counter to it. This function is thread safe.

void name_init_set2(name_t *ptr, name_t shared)

Set '*ptr' to a new shared pointer to 'shared', incrementing the user counter to the object pointed by 'shared'. This function is thread safe (providing the ptr address is local to a thread).

name_t name_init_new(void)

Allocate a new object, initialize it and return an initialized shared pointer to it. This function is thread safe if the allocator and the initialize function is.

The used allocation function is the ALLOC operator. In this case, it is assumed that the DEL operator has not been disabled.

void name_clear(name_t shared)

Clear the shared pointer, destroying the shared object if no longer any other shared pointers point to the object. This function is thread safe.

void name_set(name_t *shared1, name_t shared2)

Update the shared pointer '*shared1' to point to the same object than the shared pointer 'shared2'. Destroy the shared object pointed by '*shared1' if no longer any other shared pointers point to it, set the shared pointer 'shared' to the same object than the one pointed by 'src'. This function is thread safe.

M-I-LIST

This header is for creating intrusive doubly-linked list.

ILIST_INTERFACE(name, type)

Extend an object by adding the necessary interface to handle it within an intrusive doubly-linked list. This is the intrusive part. It shall be put within the structure of the object to link, at the top level of the structure. See example of ILIST_DEF.

ILIST_DEF(name, type[, oplist])

Define the intrusive doubly-linked list and define the associated methods to handle it as "static inline" functions. 'name' shall be a C identifier that will be used to identify the list. It will be used to create all the types and functions to handle the container. It shall be done once per type and per compilation unit.

An object is expected to be part of only one list of a kind in the entire program at a time. An intrusive list allows to move from an object to the next object without needing to go through the entire list, or to remove an object from a list in O(1). It may, or may not, be better than standard list. It depends on the context.

The object oplist is expected to have the default operators. If there is no given oplist, the methods for a standard C type are used, or if there is a global defined oplist, it is used. The created methods will use the operators to init, set and clear the contained object.

The given interface won't allocate anything to handle the objects as all allocations and initialization are let to the user.

However the objects within the list can be automatically be cleared (by calling the CLEAR method to destruct the object) on list destruction. The memory allocation, performed by the called, can also be reclaimed by defining a DEL operator to free the used memory in the object oplist. If there is no DEL operator, it is up to the user to free the used memory.

Example:

typedef struct test_s {
  int n;
  ILIST_INTERFACE (ilist_tname, struct test_s);
} test_t;

ILIST_DEF(ilist_tname, test_t)

void f(void) {
	test_t x1, x2, x3;
	ilist_tname_t list;

	x1.n = 1;
	x2.n = 2;
	x3.n = 3;

	ilist_tname_init(list);
	ilist_tname_push_back (list, &x3);
	ilist_tname_push_front (list, &x1);
	ilist_tname_push_after (&x1, &x2);

	int n = 1;
	for M_EACH(item, list, ILIST_OPLIST(ilist_tname)) {
		assert (n == item->n);
		n++;
	}
	ilist_tname_clear(list);
}

Created methods

The following methods are automatically and properly created by the previous macros. In the following methods, name stands for the name given to the macro that is used to identify the type.

name_t

Type of the list of 'type'.

name_it_t

Type of an iterator over this list.

The following methods are automatically and properly created by the previous macro.

void name_init(name_t list)

Initialize the list 'list' (aka constructor) to an empty list.

void name_init_field(type *obj)

Initialize the additional fields of the object '*obj'.

void name_clear(name_t list)

Clear the list 'list (aka destructor). The list can't be used anymore, except with a constructor. If the DEL operator is available in the oplist of the type, the cleared object will also be deleted.

void name_clean(name_t list)

Clean the list (the list becomes empty). The list remains initialized but is empty. If the DEL operator is available in the oplist of the type, the cleared object will also be deleted.

type *name_back(const name_t list)

Return a constant pointer to the data stored in the back of the list.

type *name_front(const name_t list)

Return a constant pointer to the data stored in the front of the list.

void name_push_back(name_t list, type *obj)

Push the object '*obj' itself at the back of the list 'list'.

void name_push_front(name_t list, type *obj)

Push the object '*obj' itself at the front of the list 'list'.

void name_push_after(type *position, type *obj)

Push the object '*obj' after the object '*position'.

type *name_pop_back(name_t list)

Pop the object from the back of the list 'list' and return a pointer to the popped object.

type *name_pop_front(name_t list)

Pop the object from the front of the list 'list' and return a pointer to the popped object.

bool name_empty_p(const name_t list)

Return true if the list is empty, false otherwise.

void name_swap(name_t list1, name_t list2)

Swap the list 'list1' and 'list2'.

void name_unlink(type *obj)

Remove the object '*obj' from the list.

type *name_next_obj(const name_t list, const type *obj)

Return the object that is after the object '*obj' in the list or NULL if there is no more object.

type *name_previous_obj(const name_t list, const type *obj)

Return the object that is before the object '*obj' in the list or NULL if there is no more object.

void name_it(name_it_t it, name_t list)

Set the iterator 'it' to the back(=first) element of 'list'. There is no destructor associated to this initialization.

void name_it_set(name_it_t it, const name_it_t ref)

Set the iterator 'it' to the iterator 'ref'. There is no destructor associated to this initialization.

void name_it_last(name_it_t it, name_t list)

Set the iterator 'it' to the last element of the list. There is no destructor associated to this initialization.

void name_it_end(name_it_t it, name_t list)

Set the iterator 'it' to the end of the list (i.e. not a valid element). There is no destructor associated to this initialization.

bool name_end_p(const name_it_t it)

Return true if the iterator doesn't reference a valid element anymore.

bool name_last_p(const name_it_t it)

Return true if the iterator references the last element or if the iterator doesn't reference a valid element anymore.

bool name_it_equal_p(const name_it_t it1, const name_it_t it2)

Return true if the iterator it1 references the same element than it2.

void name_next(name_it_t it)

Move the iterator 'it' to the next element of the list.

void name_previous(name_it_t it)

Move the iterator 'it' to the previous element of the list.

type *name_ref(name_it_t it)

Return a pointer to the element pointed by the iterator. This pointer remains valid until the list is modified by another method.

const type *name_cref(const name_it_t it)

Return a constant pointer to the element pointed by the iterator. This pointer remains valid until the list is modified by another method.

size_t name_size(const name_t list)

Return the number elements of the list (aka size). Return 0 if there no element.

void name_insert(name_t list, name_it_t it, type x)

Insert a copy of 'x' after the position pointed by 'it' (which is an iterator of the list 'list') or if 'it' doesn't point anymore to a valid element of the list, it is added as the back (=first) element of the 'list' This service is available only if a NEW operator is available for the type.

void name_remove(name_t list, name_it_t it)

Remove the element 'it' from the list 'list'. After wise, 'it' points to the next element of the list.

void name_splice_back(name_t list1, name_t list2, name_it_t it)

Move the element pointed by 'it' (which is an iterator of 'list2') from the list 'list2' to the back position of 'list1'. After wise, 'it' points to the next element of 'list2'.

void name_splice(name_t list1, name_t list2)

Move all the element of 'list2' into 'list1", moving the last element of 'list2' after the first element of 'list1'. After-wise, 'list2' is emptied.

M-CONCURRENT

This header is for transforming a container (LIST, ARRAY, DICT, DEQUE, ...) into an equivalent container but compatible with concurrent access by different threads. In practice, it put a lock to access the container.

As such it is quite generic. However it is less efficient than containers specialy tuned for multiple threads. There is also no iterators.

methods

CONCURRENT_DEF(name, type[, oplist])

Define the concurrent container 'name' based on container 'type' of oplist 'oplist', and define the associated methods to handle it as "static inline" functions. 'name' shall be a C identifier that will be used to identify the list. It will be used to create all the types and functions to handle the container. It shall be done once per type and per compilation unit.

Example:

    /* Define a stack container (STACK)*/
    ARRAY_DEF(array1, int)
    CONCURRENT_DEF(parray1, array1_t, ARRAY_OPLIST(array1))

    /* Define a queue container (FIFO) */
    DEQUE_DEF(deque_uint, unsigned int)
    CONCURRENT_DEF(cdeque_uint, deque_uint_t, M_OPEXTEND(DEQUE_OPLIST(deque_uint, M_DEFAULT_OPLIST), PUSH(deque_uint_push_front)))

extern parray1_t x1;
extern cdeque_uint_t x2;

void f(void) {
     parray1_push (x1, 17);
     cdeque_uint_push (x2, 17);
}

Created methods

The following methods are automatically and properly created by the previous macros. In the following methods, name stands for the name given to the macro that is used to identify the type.

name_t

Type of the concurrent container of 'type'.

void name_init(name_t concurrent)

Initialize the concurrent container. This method is only defined if the base container exports the INIT operator.

void name_init_set(name_t concurrent, const name_t src)

Initialize the concurrent container and set it with a copy of 'src'. This method is only defined if the base container exports the INIT_SET operator.

void name_init_move(name_t concurrent, name_t src)

Initialize the concurrent container by stealing as much ressources from 'src' as possible. Afterwards 'src' is cleared. This method is only defined if the base container exports the INIT_MOVE operator.

void name_set(name_t concurrent, const name_t src)

Set the container with a copy of 'src'. This method is only defined if the base container exports the SET operator.

void name_move(name_t concurrent, name_t src)

Set the container with the value of 'src' by stealing as much resources from 'src' as possible. Afterwards 'src' is cleared. This method is only defined if the base container exports the MOVE operator.

void name_clean(name_t concurrent)

Clean the concurrent container. Afterwards the container is empty, but remains initilized. This method is only defined if the base container exports the CLEAN operator.

void name_clear(name_t concurrent)

Clear the concurrent container and destroy any resource. This method shall only be called in context when no other threads can use the resource. This method is only defined if the base container exports the CLEAR operator.

void name_clear(name_t concurrent)

Clear the concurrent container and destroy any resource. This method shall only be called in context when no other threads can use the resource. This method is only defined if the base container exports the CLEAR operator.

void name_swap(name_t concurrent1, name_t concurrent2)

Swap both containers. This method is only defined if the base container exports the SWAP operator.

bool name_empty_p(const name_t concurrent)

Return true if the container is empty, false otherwise. This method is only defined if the base container exports the TEST_EMPTY operator.

void name_set_at(name_t concurrent, key_t key, value_t value)

Associate to the key 'key' the value 'value' in the container. This method is only defined if the base container exports the SET_KEY operator.

bool name_get_copy(value_t *value, name_t concurrent, key_t key)

Read the value associated to the key 'key'. If it exists, it sets '*value' to it and returns true. Otherwise it returns false. This method is only defined if the base container exports the GET_KEY operator.

void name_get_at_copy(value_t *value, name_t concurrent, key_t key)

Read the value associated to the key 'key'. If it exists, it sets '*value' to it. Otherwise it creates a new value and sets '*value' to it. This method is only defined if the base container exports the GET_SET_KEY operator.

bool name_erase(name_t concurrent, const key_t key)

Erase the association for the key 'key'. Returns true in case of success, false otherwise. This method is only defined if the base container exports the ERASE_KEY operator.

void name_push(name_t concurrent, const subtype_t data)

Push data in the container. This method is only defined if the base container exports the PUSH operator.

void name_pop(subtype_t *data, name_t concurrent)

Pop data from the container and set it in '*data'. There shall be at least one data to pop, otherwise it is undefined behavior. Testing with TEST_EMPTY before calling this function is not enough as there can be some concurrent scenario where another thread pop the last value. It is highly recomment to use name_pop_blocking instead which is safer. This method is only defined if the base container exports the POP operator.

void name_push_move(name_t concurrent, subtype_t data)

Push data in the container by stealing as much resources from data as possible. Afterwards, data is cleared. This method is only defined if the base container exports the PUSH_MOVE operator.

void name_pop_move(subtype_t *data, name_t concurrent)

Pop data from the container and initialize '*data' with it. It is highly recomment to use name_pop_move_blocking instead which is safer. This method is only defined if the base container exports the POP_MOVE operator.

void name_get_str(string_t str, name_t concurrent, bool append)

Convert the container into a string representation of it and put it in 'str' This method is only defined if the base container exports the GET_STR operator.

void name_out_str(FILE *file, name_t concurrent)

Convert the container into a string and put it in 'fil'. This method is only defined if the base container exports the OUT_STR operator.

bool name_parse_str(name_t concurrent, const char str[], const char **end)

Convert the string representing the container and set it 'concurrent' to it. Return true in case of success, false otherwise. This method is only defined if the base container exports the PARSE_STR operator.

bool name_in_str(name_t concurrent, FILE *file)

Read the file and convert the string representing the container and set it 'concurrent' to it. Return true in case of success, false otherwise. This method is only defined if the base container exports the IN_STR operator.

bool name_equal_p(name_t concurrent1, name_t concurrent2)

Return true if both containers are equal, false otherwise. This method is only defined if the base container exports the EQUAL operator.

bool name_get_blocking(value_t *value, name_t concurrent, key_t key, bool blocking)

Read the value associated to the key 'key'. If it exists, it sets '*value' to it and returns true. Otherwise if blocking is true, it waits for the data to be filled. After the wait, it sets '*value' to it and returns true. Otherwise if blocking is false, it returns false. This method is only defined if the base container exports the GET_KEY operator.

bool name_pop_blocking(type_t *data, name_t concurrent, bool blocking)

Pop a value from the container and set '*data' with it. If the container is not empty, it sets '*data' and return true. Otherwise if blocking is true, it waits for the data to be pushed. After the wait, it sets '*data' to it and returns true. Otherwise if blocking is false, it returns false. This method is only defined if the base container exports the POP and TEST_EMPTY operators.

bool name_pop_move_blocking(type_t *data, name_t concurrent, bool blocking)

Pop a value from the container and initialize & set '*data' with it. If the container is not empty, it initializes & sets '*data' and return true. Otherwise if blocking is true, it waits for the data to be pushed. After the wait, it initializes & sets '*data' to it and returns true. Otherwise if blocking is false, it returns false (*data remains uninitialized!). This method is only defined if the base container exports the POP_MOVE and TEST_EMPTY operators.

size_t name_hash(name_t concurrent)

Return a value suitable for being a hash of the container. This method is only defined if the base container exports the HASH operator.

M-BITSET

This header is for using bitset.

A bitset can be seen as a specialized version of an array of bool, where each item takes only 1 bit. It allows for compact representation of such array.

Example:

void f(void) {
	bitset_t set;
	bitset_init(set);
	for(int i = 0; i < 100; i ++)
		bitset_push_back(set, i%2);
	bitset_clear(set);
}

methods

The methods are mostly the same than for an array. The following methods are available:

TODO: document the API.

M-STRING

This header is for using dynamic string. The size of the string is automatically updated in function of the needs.

Example:

void f(void) {
	string_t s1;
	string_init (s1);
	string_set_str (s1, "Hello, world!");
	string_clear(s1);
}

methods

The following methods are available:

string_t

This type defines a dynamic string and is the primary type of the module. The provided methods are just handy wrappers to the C library. It only provides few algorithms on its own. (Typically replacement is not designed to be fast on huge strings).

STRING_FAILURE

Constant Macro defined as the index value returned in case of error. (equal as -1U).

string_fgets_t

This type defines the different enumerate value for the fgets function:

  • STRING_READ_LINE: read a full line until the EOL character (included),
  • STRING_READ_PURE_LINE: read a full line until the EOL character (excluded),
  • STRING_READ_FILE: read the full file.
void string_init(string_t str)

Init the string 'str' to an empty string.

void string_clear(string_t str)

Clear the string 'str' and frees any allocated memory.

char *string_clear_get_str(string_t v)

Clear the string 'str' and returns the allocated array of char, representing a C string, giving ownership of the array to the caller. This array will have to be freed. It can return NULL if no array was allocated by the string.

void string_clean(string_t str)

Set the string 'str' to an empty string.

size_t string_size(const string_t str)

Return the size in bytes of the string. It can be also the number of characters of the string if the encoding type is one character per byte. If the character are encoded as UTF8, the function string_length_u is preferred.

size_t string_capacity(const string_t str)

Return the capacity in bytes of the string. The capacity is the number of bytes the string accept before a reallocation of the underlying array of char has to be performed.

char string_get_char(const string_t v, size_t index)

Return the byte 'index' of the string 'v'. 'index' shall be within the allowed range of bytes of the string.

bool string_empty_p(const string_t v)

Return true if the string is empty, false otherwise.

void string_reserve(string_t v, size_t alloc)

Update the capacity of the string to be able to receive at least 'alloc' bytes. Calling with 'alloc' lower or equal than the size of the string allows the function to perform a shrink of the string to its exact needs. If the string is empty, it will free the memory.

void string_set_str(string_t v, const char str[])

Set the string to the array of char 'str'. 'str' is supposed to be 0 terminated as any C string.

void string_set_strn(string_t v, const char str[], size_t n)

Set the string to the array of char 'str' by copying at most 'n' char from the array.'str' is supposed to be 0 terminated as any C string.

const char* string_get_cstr(const string_t v)

Return a constant pointer to the underlying array of char of the string. This array of char is terminated by 0, allowing the pointer to be passed to standard C function.

void string_set (string_t v1, const string_t v2)

Set the string 'v1' to the value of the string 'v2'.

void string_set_n(string_t v, const string_t ref, size_t offset, size_t length)

Set the string to the value of the string 'ref' by skipping the first 'offset' char of the array of char of 'ref' and copying at most 'length' char in the remaining array of characters of 'ref'. 'offset' shall be within the range of index of the string 'ref'. 'ref' and 'v' cannot be the same string.

void string_init_set(string_t v1, const string_t v2)

Initialize 'v1' to the value of the string 'v2'.

void string_init_set_str(string_t v1, const char str[])

Initialize 'v1' to the value of the array of char 'str'. The array of char shall be terminated with 0.

void string_init_move(string_t v1, string_t v2)

Initialize 'v1' by stealing as most resource from 'v2' as possible and clear 'v2' afterward.

void string_move(string_t v1, string_t v2)

Set 'v1' by stealing as most resource from 'v2' as possible and clear 'v2' afterward.

void string_swap(string_t v1, string_t v2)

Swap the content of both strings.

void string_push_back (string_t v, char c)

Append the string with the character 'c'

void string_cat_str(string_t v, const char str[])

Append the string with the array of char 'str'. The array of char shall be terminated with 0.

void string_cat(string_t v, const string_t v2)

Append the string with the string 'v2'.

int string_cmp_str(const string_t v1, const char str[])

Perform a byte comparison of the string and the array of char by using the strcmp function and return the result of this comparison.

int string_cmp(const string_t v1, const string_t v2)

Perform a byte comparison of both string by using the strcmp function and return the result of this comparison.

bool string_equal_str_p(const string_t v1, const char str[])

Return true if the string is equal to the array of char, false otherwise.

bool string_equal_p(const string_t v1, const string_t v2)

Return true if both strings are equal, false otherwise.

int string_cmpi_str(const string_t v1, const char p2[])

This function compares the string and the array of char by ignoring the difference due to the case. This function doesn't work with UTF-8 strings. It returns a negative integer if the string is before the array, 0 if there are equal, a positive integer if the string is after the array.

int string_cmpi(const string_t v1, const string_t v2)

This function compares both strings by ignoring the difference due to the casse. This function doesn't work with UTF-8 strings. It returns a negative integer if the string is before the array, 0 if there are equal, a positive integer if the string is after the array.

size_t string_search_char (const string_t v, char c [, size_t start])

Search for the character 'c' in the string from the offset 'start'. 'start' shall be within the valid ranges of offset of the string. 'start' is an optional argument. If it is not present, the default value 0 is used instead. This doesn't work if the function is used as function pointer. Return the offset of the string where the character is first found, or STRING_FAILURE otherwise.

size_t string_search_rchar (const string_t v, char c [, size_t start])

Search backwards for the character 'c' in the string from the offset 'start'. 'start' shall be within the valid ranges of offset of the string. 'start' is an optional argument. If it is not present, the default value 0 is used instead. This doesn't work if the function is used as function pointer. Return the offset of the string where the character is last found, or STRING_FAILURE otherwise.

size_t string_search_str (const string_t v, char str[] [, size_t start])

Search for the array of char 'str' in the string from the offset 'start'. 'start' shall be within the valid ranges of offset of the string. 'start' is an optional argument. If it is not present, the default value 0 is used instead. This doesn't work if the function is used as function pointer. Return the offset of the string where the array of char is first found, or STRING_FAILURE otherwise.

size_t string_search (const string_t v, string_t str [, size_t start])

Search for the string 'str' in the string from the offset 'start'. 'start' shall be within the valid ranges of offset of the string. 'start' is an optional argument. If it is not present, the default value 0 is used instead. This doesn't work if the function is used as function pointer. Return the offset of the string where 'str' is first found, or STRING_FAILURE otherwise.

size_t string_pbrk(const string_t v, const char first_of[] [, size_t start])

Search for the first occurrence in the string 'v' from the offset 'start' of any of the bytes in the string 'first_of'. 'start' shall be within the valid ranges of offset of the string. 'start' is an optional argument. If it is not present, the default value 0 is used instead. This doesn't work if the function is used as function pointer. Return the offset of the string where 'str' is first found, or STRING_FAILURE otherwise.

int string_strcoll_str(const string_t str1, const char str2[])
int string_strcoll(const string_t str1, const string_t str2[])

Compare the two strings str1 and str2. It returns an integer less than, equal to, or greater than zero if s1 is found, respectively, to be less than, to match, or be greater than s2. The comparison is based on strings interpreted as appropriate for the program's current locale.

size_t string_spn(const string_t v1, const char accept[])

Calculate the length (in bytes) of the initial segment of the string that consists entirely of bytes in accept.

size_t string_cspn(const string_t v1, const char reject[])

Calculate the length (in bytes) of the initial segment of the string that consists entirely of bytes not in reject.

void string_left(string_t v, size_t index)

Keep at most the 'index' left bytes of the string, terminating the string at the given index. index can be out of range.

void string_right(string_t v, size_t index)

Keep the right part of the string, after the index 'index'.

void string_mid (string_t v, size_t index, size_t size)

Extract the medium string from offset 'index' and up to 'size' bytes.

size_t string_replace_str (string_t v, const char str1[], const char str2[] [, size_t start])
size_t string_replace (string_t v, const string_t str1, const string_t str2 [ , size_t start])

Replace in the string 'v' from the offset start the string str1 by the string str2 once. Returns the offset of the replacement or STRING_FAILURE if no replacement was performed.

void string_replace_at (string_t v, size_t pos, size_t len, const char str2[])

Replace in the string 'v' the sub-string defined as starting from 'pos' and of size 'len' by the string str2. It assumes that pos+len is within the range.

int string_printf (string_t v, const char format[], ...)

Set the string 'v' to the formatted string format. 'format' follows the printf function family.

int string_cat_tprintf (string_t v, const char format[], ...)

Appends to the string 'v' the formatted string format. 'format' follows the printf function family.

bool string_fgets(string_t v, FILE *f, string_fgets_t arg)

Read from the opened file 'f' a stream of characters and set 'v' with this stream. It stops after the character end of line if arg is STRING_READ_PURE_LINE or STRING_READ_LINE, and until the end of the file if arg is STRING_READ_FILE. If arf is STRING_READ_PURE_LINE, the character end of line is removed from the string. Return true if something has been read, false otherwise.

bool string_fget_word (string_t v, const char separator[], FILE *f)

Read a word from the file 'f' and set 'v' with this word. A word is separated from another by the list of characters in the array 'separator'. (Example: "^ \t.\n"). It is highly recommended for separator to be a constant string.

void string_fputs(FILE *f, const string_t v)

Put the string in the file.

bool string_start_with_str_p(const string_t v, const char str[])
bool string_start_with_string_p(const string_t v, const string_t str)

Return true if the string start with the same characters than 'str', false otherwise.

size_t string_hash(const string_t v)

Return a hash of the string.

void string_strim(string_t v [, const char charTab[]])

Remove from the string any leading or trailing space-like characters (space or tabulation or end of line). If charTab is given, it get the list of characters to remove from this argument.

void string_get_str(string_t v, const string_t v2, bool append)

Convert a string into a string usable for I/O: Outputs the input string with quote around, replacing any " by \" within the string into the output string.

bool string_parse_str(string_t v, const char str[], const char **endp)

Parse the string 'str' that is assumed to be a string representation of a string and set 'v' to this representation. This method is only defined if the type of the element defines a PARSE_STR method itself. It returns true if success, false otherwise. If endp is not NULL, it sets '*endp' to the pointer of the first character not decoded by the function.

void string_out_str(FILE *f, const string_t v)

Write a string into a FILE: Outputs the input string with quote around, replacing any " by \" within the string.

bool string_in_str(string_t v, FILE *f)

Read a string from a FILE. The string shall have be written by string_out_str. It returns true if it has successfully parsed the string, false otherwise. In this case, the position within the FILE is undefined.

string_unicode_t

Define a type suitable to store a unicode character.

string_it_t

Define an iterator over the string, allowing to iterate the string over UTF8 encoded character.

void string_it(string_it_t it, const string_t str)

Initialize the iterator 'it' to iterate over the string 'str' over UTF8 encoded character.

bool string_end_p (string_it_t it)

Return true if the iterator has reached the end of the string, false otherwise.

void string_next (string_it_t it)

Move the iterator to the next UTF8 encoded character. It is assumed that string_end_p has been called at least once per UTF8 character before.

string_unicode_t string_get_cref (const string_it_t it)

Return the unicode character associated to the UTF8 encoded character pointer by the iterator. It is assumed that string_end_p has been called at least once per UTF8 character before. It returns -1 in case of error in decoding the UTF8 string.

void string_push_u (string_t str, string_unicode_t u)

Push the unicode character 'u' into the string 'str' encoding it as a UTF8 encoded characters.

size_t string_length_u(string_t str)

Return the number of UTF8 encoded characters in the string.

bool string_utf8_p(string_t str)

Return true if the string is a valid UTF8, false otherwise. It doesn't check for unique canonical form for UTF8 string, so it may report 'true' whereas the string is not strictly conforming.

STRING_CTE(string)

Macro to convert a constant array string into a temporary string_t variable suitable only for being called within a function.

STRING_OPLIST

The oplist of a string_t

BOUNDED_STRING_DEF(name, size)

aka char[N+1] TODO: Document the API.

M-CORE

This header is the internal core of M*LIB, providing a lot of functionality by extending a lot the preprocessing capability. Working with these macros is not easy and the developer needs to know how the macro preprocessing works. It also adds the needed macro for handling the oplist. As a consequence, it is needed by all other header files.

Some macros are using recursivity to work. This is not an easy feat to do as it needs some tricks to work (see reference). This still work well with only one major limitation: it can not be chained. For example, if MACRO is a macro implementing recursivity, then MACRO(MACRO()) won't work.

Example:

M_MAP(f, 1, 2, 3, 4)
M_REDUCE(f, g, 1, 2, 3, 4)
M_SEQ(1, 20, f)

Compiler Macros

The following compiler macros are available:

M_ASSUME(cond)

M_ASSUME is equivalent to assert, but gives hints to compiler about how to optimize the code if NDEBUG is defined.

M_LIKELY(cond) / M_UNLIKELY(cond)

M_LIKELY / M_UNLIKELY gives hints on the compiler of the likelihood of the given condition.

Preprocessing macro extension

M_MAX_NB_ARGUMENT

Maximum number of argument that can be handled by this header.

M_C(a,b), M_C3(a,b,c), M_C4(a,b,c,d)

Return a symbol corresponding to the concatenation of the input arguments.

M_INC(number)

Increment the number given as argument (from [0..52]) and return a pre-processing token corresponding to this value (meaning it is evaluated at macro processing stage, not at compiler stage). If number is not within the range, the behavior is undefined.

M_DEC(number)

Decrement the number given as argument (from [0..52]) and return a pre-processing token corresponding to this value (meaning it is evaluated at macro processing stage, not at compiler stage). If number is not within the range, the behavior is undefined.

M_ADD(x, y)

Return x+y (resolution is performed at preprocessing time). x and y shall be within [0..52].

M_SUB(x, y)

Return x-y (resolution is performed at preprocessing time). x and y shall be within [0..52] and x >= y.

M_RET_ARG1(arglist,...) / M_RET_ARGN(...)

Return the argument 1 of the given arglist (respectively 2 and N, with which is within [2..53]). The argument shall exist in the arglist.

M_SKIP(N,...)

Skip the Nth first arguments of the argument list. N can be within [0..52].

M_KEEP(N,...)

Keep the Nth first arguments of the argument list. N can be within [0..52].

M_MID(first, len,...)

Keep the medium arguments of the argument list, starting from the 'first'-th one and up to 'len' arguments. first and len can be within [0..52].

M_GET_AT(array, index)

Return the index 'index' of the "array" 'array'. array in this context is a list of arguments encapsulated with parenthesis, and is not a true C array. Return the pre-processing token corresponding to this value (meaning it is evaluated at macro processing stage, not at compiler stage).

Example:

    M_GET_AT((f_0,f_1,f_2),1)
M_BOOL(cond)

Convert an integer or a symbol into 0 (if 0) or 1 (if not 0 or symbol unknown). Return a pre-processing token corresponding to this value (meaning it is evaluated at macro processing stage, not at compiler stage).

M_INV(cond)

Inverse 0 into 1 and 1 into 0. It is undefined if cond is not 0 or 1 (use M_BOOL to convert). Return a pre-processing token corresponding to this value (meaning it is evaluated at macro processing stage, not at compiler stage).

M_AND(cond1, cond2)

Perform a logical 'and' between cond1 and cond2. cond1 and cond2 shall be 0 or 1 otherwise it is undefined (You shall use M_bool otherwise). Return a pre-processing token corresponding to this value (meaning it is evaluated at macro processing stage, not at compiler stage).

M_OR(cond1, cond2)

Perform a logical 'or' between cond1 and cond2. cond1 and cond2 shall be 0 or 1 otherwise it is undefined. (You shall use M_bool otherwise). Return a pre-processing token corresponding to this value (meaning it is evaluated at macro processing stage, not at compiler stage).

M_IF(cond)(action_if_true, action_if_false)

Return the pre-processing token 'action_if_true' if 'cond' is true, action_if_false otherwise (meaning it is evaluated at macro processing stage, not at compiler stage). cond shall be 0 or 1 otherwise it is undefined.

M_COMMA_P(arglist)

Return 1 if there is a comma inside the argument list, 0 otherwise. Return a pre-processing token corresponding to this value (meaning it is evaluated at macro processing stage, not at compiler stage).

M_AS_STR(expression)

Return the string representation of the evaluated expression. NOTE: Need to be used with M_APPLY to defer the evaluation.

M_EMPTY_P(expression)

Return 1 if the argument 'expression' is 'empty', 0 otherwise. Return a pre-processing token corresponding to this value (meaning it is evaluated at macro processing stage, not at compiler stage).

M_DEFERRED_COMMA

Return a comma ',' at a later phase of the macro processing steps.

M_IF_EMPTY(cond)(action_if_true, action_if_false)

Return the pre-processing token 'action_if_true' if 'cond' is empty, action_if_false otherwise (meaning it is evaluated at macro processing stage, not at compiler stage). cond shall be 0 or 1 otherwise it is undefined.

M_PARENTHESIS_P(expression)

Return 1 if the argument 'expression' starts a parenthesis and ends it (like '(...)'), 0 otherwise. Return a pre-processing token corresponding to this value (meaning it is evaluated at macro processing stage, not at compiler stage).

M_DELAY1(expr) / M_DELAY2(expr) / M_DELAY3(expr) / M_DELAY4(expr) / M_ID

Delay the evaluation by 1, 2, 3 or 4 steps. This is necessary to write macros that are recursive. The argument is a macro-function that has to be deferred. M_ID is an equivalent of M_DELAY1.

M_EVAL(expr)

Perform a complete stage evaluation of the given expression, removing recursive expression within it. Only ONE M_EVAL expression is expected in the evaluation chain. Can not be chained.

M_APPLY(func, args...)

Apply 'func' to '(args...) ensuring that a() isn't evaluated until all 'args' have been also evaluated. It is used to delay evaluation.

M_MAP(func, args...)

Apply 'func' to each argument of the 'args...' list of argument.

M_MAP_C(func, args...)

Apply 'func' to each argument of the 'args...' list of argument, putting a comma between each expanded 'func(argc)'

M_MAP2(func, data, args...)

Apply 'func' to each couple '(data, argument)' with argument an element of the 'args...' list.

M_MAP2_c(func, data, args...)

Apply 'func' to each couple '(data, argument)' with argument an element of the 'args...' list, putting a comma between each expanded 'func(argc)'

M_MAP_PAIR(func, args...)

Map a macro to all given pair of arguments (Using recursivity). Can not be chained.

M_REDUCE(funcMap, funcReduce, args...)

Map the macro funcMap to all given arguments 'args' and reduce all theses computation with the macro 'funcReduce'. Example: M_REDUCE(f, g, a, b, c) ==> g( f(a), g( f(b), f(c))

M_REDUCE2(funcMap, funcReduce, data, args...)

Map the macro funcMap to all pair (data, arg) of the given argument list 'args' and reduce all theses computation with the macro 'funcReduce'. Do not use recursivity.

M_SEQ(init, end, macro, data)

Generate a sequence of number from 'init' to 'end' and apply to the macro the pair '(data, num)' for each number 'num'.

M_EAT(...)

Clobber the input, whatever it is.

M_NARGS(args...)

Return the number of argument of the given list. Doesn't work for empty argument.

M_IF_NARGS_EQ1(argslist)(action_if_one_arg, action_otherwise)

Return the pre-processing token 'action_if_one_arg' if 'argslist' has only one argument, action_otherwise otherwise (meaning it is evaluated at macro processing stage, not at compiler stage).

M_IF_NARGS_EQ2(argslist)(action_if_two_arg, action_otherwise)

Return the pre-processing token 'action_if_two_arg' if 'argslist' has two arguments, action_otherwise otherwise (meaning it is evaluated at macro processing stage, not at compiler stage).

M_IF_DEBUG(action)

Return the pre-processing token 'action' if the build is compiled in debug mode (i.e. NDEBUG is undefined).

M_IF_DEFAULT1(default_value, argumentlist)

Helper macro to redefine a function with a default value. If there is only one variable as the argument list, print the variable of the argument list then ', value', instead only print the argument list (and so two arguments). Example: int f(int a, int b); #define f(...) M_APPLY(f, M_IF_DEFAULT1(0, VA_ARGS)) This need to be called within a M_APPLY macro.

M_DEFAULT_ARGS(nbExpectedArg, (defaultArgumentlist), argumentList )

Helper macro to redefine a function with one or more default values. defaultArgumentlist is a list of the default value to complete the list argumentList to reach the number nbExpectedArg arguments. Example: int f(int a, int b, long p, void *q); #define f(...) f(M_DEFAULT_ARGS(4, (0, 1, NULL), VA_ARGS)) The last 3 arguments have their default value as 0 (for b), 1 (for p) and NULL (for q).

M_NOTEQUAL(x, y)

Return 1 if x != y, 0 otherwise (resolution is performed at preprocessing time). x and y shall be within the maximum argument value.

M_EQUAL(x, y)

Return 1 if x == y, 0 otherwise (resolution is performed at preprocessing time). x and y shall be within the maximum argument value.

M_INVERT(args...)

Reverse the argument list. For example, if args was a,b,c, it return c,b,a.

C11 Macro

Theses macros are only valid if the program is built in C11 mode:

M_PRINTF_FORMAT(x)

Return the printf format associated to the type of 'x'. 'x' shall be printable with printf.

M_PRINT_ARG(x)

Print using printf the variable 'x'. The format of 'x' is deduced provided that it is a standard numerical C type.

M_FPRINT_ARG(file, x)

Print into a file 'file' using fprintf the variable 'x'. The format of 'x' is deduced provided that it is a standard numerical C type.

M_GET_STRING_ARG(string,x,append)

Print into the string_t 'string' the variable 'x'. The format of 'x' is deduced provided that it is a standard numerical C type. It needs the header 'm-string.h' for working (this macro is only a wrapper around it).

M_PRINT(args...)

Print using printf all the variable in 'args'. The format of the arguments are deduced provided that it is a standard numerical C type.

M_FPRINT(file, args...)

Print into a file 'file' using fprintf all the variables in 'args'. The format of the arguments are deduced provided that it is a standard numerical C type.

M_AS_TYPE(type, x)

Within a C11 _Generic statement, all expressions shall be valid C expression even if the case if always false, and is not executed. This can seriously limit the _Generic statement. This macro overcomes this limitation by returning :

  • either the input 'x' if it is of type 'type',
  • or the value 0 view as a type 'type'.

So the returned value is always of type 'type' and is safe in a _Generic statement.

C Macro

Theses macros expand their code at compilation level:

M_MIN(x, y)

Return the minimum of 'x' and 'y'. x and y shall not have any side effect.

M_MAX(x, y)

Return the maximum of 'x' and 'y'. x and y shall not have any side effect.

M_POWEROF2_P(n)

Return true if 'n' is a power of 2. n shall not have any side effect.

M_SWAP(type, a, b)

Swap the values of 'a' and 'b'. 'a' and 'b' are of type 'type'. a and b shall not have any side effect.

M_ASSIGN_CAST(type, a)

Check if 'a' can be assigned to a temporary of type 'type' and returns this temporary. If it cannot, the compilation failed.

M_CONST_CAST(type, a)

Check if 'a' can be properly casted to (const type *) and returns this casted pointer if possible. If it cannot, the compilation failed.

M_TYPE_FROM_FIELD(type, ptr, fieldType, field)

Assuming 'ptr' is a pointer to a fieldType object that is stored within a structure of type 'type' at the position 'field', it returns a pointer to the structure.

HASH Functions

M_HASH_SEED --> size_t

User shall overwrite this macro by a random seed (of type size_t) before including the header m-core.h o that all hash functions will use this variable as a seed for the hash functions. If no user macro is defined, the default is to expand it to 0, making all hash computations predictable.

M_HASH_DECL(hash)

Declare and initialize a new hash computation, named 'hash' that is an integer.

M_HASH_UP(hash, value)

Update the 'hash' variable with the given 'value' by incorporating the 'value' within the 'hash'. 'value' can be up to a 'size_t' variable.

uint32_t m_core_rotl32a (uint32_t x, uint32_t n)
uint64_t m_core_rotl64a (uint64_t x, uint32_t n)

Perform a rotation of 'n' bits of the input 'x'. n shall be within 1 and the number of bits of the type minus 1.

uint64_t m_core_roundpow2(uint64_t v)

Round to the next highest power of 2.

unsigned int m_core_clz32(uint32_t limb)
unsigned int m_core_clz64(uint64_t limb)

Count the number of leading zero and return it. limb can be 0.

size_t m_core_hash (const void *str, size_t length)

Compute the hash of the binary representation of the data pointer by 'str' of length 'length'. 'str' shall have the same alignment restriction than a 'size_t'.

OPERATORS Functions

M_GET_INIT oplist
M_GET_INIT_SET oplist
M_GET_INIT_MOVE oplist
M_GET_SET oplist
M_GET_MOVE oplist
M_GET_SWAP oplist
M_GET_CLEAR oplist
M_GET_NEW oplist
M_GET_DEL oplist
M_GET_REALLOC oplist
M_GET_FREE oplist
M_GET_MEMPOOL oplist
M_GET_MEMPOOL_LINKAGE oplist
M_GET_HASH oplist
M_GET_EQUAL oplist
M_GET_CMP oplist
M_GET_UPDATE oplist
M_GET_TYPE oplist
M_GET_SUBTYPE oplist
M_GET_OPLIST oplist
M_GET_SORT oplist
M_GET_IT_TYPE oplist
M_GET_IT_FIRST oplist
M_GET_IT_LAST oplist
M_GET_IT_END oplist
M_GET_IT_SET oplist
M_GET_IT_END_P oplist
M_GET_IT_LAST_P oplist
M_GET_IT_EQUAL_P oplist
M_GET_IT_NEXT oplist
M_GET_IT_PREVIOUS oplist
M_GET_IT_REF oplist
M_GET_IT_CREF oplist
M_GET_IT_REMOVE oplist
M_GET_IT_INSERT oplist
M_GET_ADD oplist
M_GET_SUB oplist
M_GET_MUL oplist
M_GET_DIV oplist
M_GET_CLEAN oplist
M_GET_PUSH oplist
M_GET_POP oplist
M_GET_REVERSE oplist
M_GET_GET_STR oplist
M_GET_OUT_STR oplist
M_GET_IN_STR oplist
M_GET_SEPARATOR oplist
M_GET_EXT_ALGO oplist
M_GET_INC_ALLOC oplist
M_GET_OOR_SET oplist
M_GET_OOR_EQUAL oplist

Return the associated method to the given operator within the given oplist.

M_DEFAULT_OPLIST

Default oplist for C standard types (int & float)

M_CSTR_OPLIST

Default oplist for the C type const char *

M_POD_OPLIST

Default oplist for a structure C type without any init & clear prerequisites.

M_A1_OPLIST

Default oplist for a array of size 1 of a structure C type without any init & clear prerequisites.

M_CLASSIC_OPLIST(name)

Create the oplist with the classic operators using the pattern 'name', i.e. name##_init, name##_clear, etc.

M_OPFLAT oplist

Remove the parenthesis around an oplist.

M_OPCAT(oplist1,oplist2)

Concat two oplists in one. 'oplist1''s operators will have higher priority to 'oplist2'

M_OPEXTEND(oplist, ...)

Extend an oplist with the given list of operators. Theses new operators will have higher priority than the ones in the given oplist.

M_TEST_METHOD_P(method, oplist)

Test if a method is present in an oplist. Return 0 or 1.

M_IF_METHOD(method, oplist)

Perform a preprocessing M_IF, if the method is present in the oplist. Example: M_IF_METHOD(HASH, oplist)(define function that uses HASH method, )

M_IF_METHOD_BOTH(method, oplist1, oplist2)

Perform a preprocessing M_IF if the method exists in both oplist. Example: M_IF_METHOD_BOTH(HASH, oplist1, oplist2) (define function , )

M_IF_METHOD_ALL(method, ...)

Perform a preprocessing M_IF if the method exists for all oplist. Example: M_IF_METHOD_ALL(HASH, oplist1, oplist2, oplist3) (define function, )

M_IPTR

By putting this after a method for an operator in the oplist, it specifies that the first argument of the method shall be a pointer to the destination type, rather than the type.

M_DO_INIT_MOVE(oplist, dest, src)
M_DO_MOVE(oplist, dest, src)

Perform an INIT_MOVE/MOVE if present, or emulate it otherwise. Note: default methods for INIT_MOVE/MOVE are not robust enough yet.

M_GLOBAL_OPLIST(a)

Check if a is an oplist, and return a or if a symbol composed of M_OPL_##a() is defined as an oplist, and returns it otherwise return a. In short, if a global oplist is defined for the argument, it returns it otherwise it returns the argument. Global oplist is limited to typedef types.

M_GLOBAL_OPLIST_OR_DEF(a)

Check if a a symbol composed of M_OPL_##a() is defined as an oplist, and returns its name otherwise return a name that will expand to M_DEFAULT_OPLIST. The return value shall be evaluated once again to get the oplist (this is needed due to technical reasons).

M_GLOBAL_OPLIST_OR_DEF(mpz_t)()

In short, if a global oplist is defined for the argument, it returns it otherwise it returns the default oplist. Global oplist is limited to typedef types.

Syntax enhancing

These macros are quite useful to lighten the C style and make full use of the library.

M_EACH(item, container, oplist|type)

This macro enables to iterate over the given 'container' of oplist 'oplist' or of type 'type' with a globaly registered oplist. It shall be used after the for C keyword to perform a loop over the container. 'item' will be a created pointer variable to the underlying type of the given container. This variable is only available within the 'for' loop. There can only have one M_EACH per line. The order of the iteration depends on the given container.

Example: for M_EACH(item, list, LIST_OPLIST) { action; }

M_LET(var1[,var2[,...]], oplist|type)

This macro enables to define the variable 'var1'(resp. var2, ...) of oplist 'oplist' or of type 'type' with a globaly registered oplist. It initializes 'var1' (resp. var2, ...) by calling the initialization method, and clears 'var1' (resp. var2, ...) by calling the clear method when the bracket associated to the M_LET go out of scope.

Example:

 M_LET(a, STRING_OPLIST) { do something with a }  or
 M_LET(a, b, c, STRING_OPLIST) { do something with a, b & c }

NOTE: The user code can not perform a return or a goto outside the {} otherwise the clear code of the object won't be called . However, you can use the break instruction to quit the block.

Memory functions

type *M_MEMORY_ALLOC (type)

Return a pointer to a new allocated object of type 'type'. The object is not initialized. In case of allocation error, it returns NULL. The default function is the malloc function. It can be overridden before including the header m-core.h

void M_MEMORY_DEL (type *ptr)

Delete the cleared object pointed by the pointer 'ptr'. The pointer was previously allocated by the macro M_MEMORY_ALLOC. 'ptr' can not be NULL. The default function is the free function. It can be overridden before including the header m-core.h

type *M_MEMORY_REALLOC (type, ptr, number)

Return a pointer to an array of 'number' objects of type 'type'. The objects are not initialized, nor the state of previous objects changed. 'ptr' is either NULL, or pointer returned from a previous call of M_MEMORY_REALLOC. In case of allocation error, it returns NULL. The default function is the realloc function. It can be overridden before including the header m-core.h

void M_MEMORY_FREE (type *ptr)

Delete the cleared object pointed by the pointer 'ptr'. The pointer was previously allocated by the macro M_MEMORY_REALLOC. 'ptr' can not be NULL. The default function is the free function. It can be overridden before including the header m-core.h A pointer allocated by M_MEMORY_ALLOC can not be freed by this function.

void M_MEMORY_FULL (size_t size)

This macro is called when a memory error has been detected. It can be overridden before including the header m-core.h The default is to abort the execution. The macro can :

  • abort the execution,
  • throw an exception (In this case, the state of the object is unchanged),
  • set a global error variable and return.

NOTE: The last two cases are not properly fully supported yet.

void M_INIT_FAILURE (void)

This macro is called when an initialization error shall be raised. It can be overridden before including the header m-core.h The default is to abort the execution. The macro can :

  • abort the execution,
  • throw an exception (In this case, the state of the object is unchanged),
  • set a global error variable and return.

NOTE: The last case is not currently supported.

void M_ASSERT_INIT_FAILURE(expression)

This macro is called when an assertion in an initialization context is called. If the expression is false, the execution is aborted. The assertion is kept in release programs. It can be overridden before including the header m-core.h The default is to abort the execution.

M-MUTEX

This header is for providing very thin layer around OS implementation of threads, conditional variables and mutex. It has back-ends for WIN32, POSIX thread or C11 thread.

It was needed due to the low adoption rate of the C11 equivalent layer.

Example:

m_thread_t idx_p;
m_thread_t idx_c;

m_thread_create (idx_p, conso_function, NULL);
m_thread_create (idx_c, prod_function, NULL);
m_thread_join (idx_p;
m_thread_join (idx_c);

methods

The following methods are available:

m_mutex_t

A type representing a mutex.

void m_mutex_init(mutex)

Initialize the variable mutex of type m_mutex_t. If the initialization fails, the program aborts.

void m_mutex_clear(mutex)

Clear the variable mutex of type m_mutex_t. If the variable is not initialized, the behavior is undefined.

void m_mutex_lock(mutex)

Lock the variable mutex of type m_mutex_t for exclusive use. If the variable is not free, it will wait indefinitely until it is. If the variable is not initialized, the behavior is undefined.

void m_mutex_unlock(mutex)

Unlock the variable mutex of type m_mutex_t for exclusive use. If the variable is not locked, the behavior is undefined. If the variable is not initialized, the behavior is undefined.

M_LOCK_DECL(name)

Define the lock 'name'. This shall be called in the global space (reserved for global variables).

M_LOCK(name)

Use the lock 'name': the encapsulation instructions are protected by the lock. Example:

    M_LOCK_DECL(n_lock);
    unsigned long n = 0;
    void f(void) {
         M_LOCK(n_lock) {
           n ++;
         }
    }

m_cond_t

A type representing a conditional variable, used within a mutex section.

void m_cond_init(m_cond_t cond)

Initialize the conditional variable cond of type m_cond_t. If the initialization failed, the program aborts.

void m_cond_clear(m_cond_t cond)

Clear the variable cond of type m_cond_t. If the variable is not initialized, the behavior is undefined.

void m_cond_signal(m_cond_t cond)

Within a mutex exclusive section, signal that the event associated to the variable cond of type m_cond_t has occurred to at least a single thread. If the variable is not initialized, the behavior is undefined.

void m_cond_broadcast(m_cond_t cond)

Within a mutex exclusive section, signal that the event associated to the variable cond of type m_cond_t has occurred to all waiting threads. If the variable is not initialized, the behavior is undefined.

void m_cond_wait(m_cond_t cond, m_mutex_t mutex)

Within a mutex exclusive section defined by mutex, wait indefinitely for the event associated to the variable cond of type m_cond_t to occur. IF multiple threads wait for the same event, which thread to awoken is not specified. If any variable is not initialized, the behavior is undefined.

m_thread_t

A type representing an id of a thread.

void m_thread_create(m_thread_t thread, void (*function)(void*), void *argument)

Create a new thread and set the it of the thread to 'thread'. The new thread run the code function(argument) with argument a 'void *' and function taking a 'void *' and returning nothing. If the initialization fails, the program aborts.

void m_thread_join(m_thread_t thread)

Wait indefinitely for the thread 'thread' to exit.

M-WORKER

This header is for providing a pool of workers. Each worker run in a separate thread and can handle work orders sent by the main thread. A work order is a computation task. Work orders are organized around synchronization points.

This implements parallelism just like OpenMP or CILK++.

Example:

    worker_t worker;
    worker_init(worker, 0, 0, NULL);
    worker_sync_t sync;
    worker_start(sync, worker);
    void *data = ...;
    worker_spawn (sync, taskFunc, data);
    taskFunc(otherData);
    worker_sync(sync);

Currently, there is no support for:

  • exceptions by the worker tasks,
  • unbalanced design: the worker tasks shall not lock a mutex without closing it (same for other synchronization structures).

Thread Local Storage variables have to be reinitialized properly with the reset function. This may result in subtle difference between the serial code and the parallel code.

methods

The following methods are available:

worker_t

A pool of worker.

worker_sync_t

A synchronization point between workers.

void worker_init(worker_t worker[, unsigned int numWorker, unsigned int extraQueue, void (*resetFunc)(void), void (*clearFunc)(void) ])

Initialize the pool of workers 'worker' with 'numWorker' workers. if 'numWorker' is 0, then it will detect how many core is available on the system. Between each work order and before the first one, the function 'resetFunc' is called by the worker to reset its state (or call nothing if the function pointer is NULL). 'extraQueue' is the number of tasks that can be accepted by the work order queue in case if there is no worker available. Before terminating, each worker will call 'clearFunc' if the function is not NULL. Default values are respectively 0, 0, NULL and NULL.

void worker_clear(worker_t worker)

Clear the pool of workers, and wait for the workers to terminate. It is undefined if there is any work order in progress.

void worker_start(worker_block_t syncBlock, worker_t worker)

Start a new synchronization block for a pool of work orders linked to the pool of worker 'worker'.

void worker_spawn(worker_block_t syncBlock, void (*func)(void *data), void *data)

Request the work order 'func(data)' to the the synchronization point 'syncBlock'. If no worker is available, the work order 'func(data)' will be handled by the caller. Otherwise the work order 'func(data)' will be handled by an asynchronous worker and the function immediately returns.

bool worker_sync_p(worker_block_t syncBlock)

Test if all work orders registered to this synchronization point are terminated (true) or not (false).

void worker_sync(worker_block_t syncBlock)

Wait for all work orders registered to this synchronization point 'syncBlock' to be terminated.

size_t worker_count(worker_t worker)

Return the number of workers of the pool.

WORKER_SPAWN(syncBlock, input, core, output)

Request the work order '_core' to the synchronization point syncBlock. If no worker is available, the work order 'core' will be handled by the caller. Otherwise the work order 'core' will be handled by an asynchronous worker. 'core' is any C code that doesn't break the control flow (you cannot use return / goto to go outside the flow). 'input' is the list of input variables of the 'core' block within "( )". 'output' is the list of output variables of the 'core' block within "( )". This macro needs either GCC (for nested function) or CLANG (for blocks) or a C++11 compiler (for lambda and functional) to work.

NOTE1: Even if nested functions are used for GCC, it doesn't generate a trampoline and the stack doesn't need to be executable.

NOTE2: For CLANG, you need to add -fblocks to CFLAGS and -lBlocksRuntime to LIB (See CLANG manual).

NOTE3: It will generate warnings about shadow variables. There is no way to avoid this.

NOTE4: arrays are not supported as input / output variables due to technical limitations.

M-ATOMIC

This header goal is to provide the C header 'stdatomic.h' to any C compiler (C11 or C99 compliant) or C++ compiler. If available, it uses the C11 header stdatomic.h, otherwise if the compiler is a C++ compiler, it uses the header 'atomic' and imports all definition into the global namespace (this is needed because the C++ standard doesn't support officially the stdatomic header, resulting in broken compilation when building C code with a C++ compiler). Otherwise it provides a non-thin emulation of atomic using mutex.

M-ALGO

This header is for generating generic algorithm to containers.

ALGO_DEF(name, container_oplist)

Define the available algorithms for the container which oplist is container_oplist. The defined algorithms depend on the availability of the methods of the containers (For example, if there no CMP operation, there is no MIN operation defined).

Example:

ARRAY_DEF(array_int, int)
ALGO_DEF(array_int, ARRAY_OPLIST(int))
void f(void) {
	array_int_t l;
	array_int_init(l);
	for(int i = 0; i < 100; i++)
		array_int_push_back (l, i);
	array_int_push_back (l, 17);
	assert( array_int_contains(l, 62) == true);
	assert( array_int_contains(l, -1) == false);
	assert( array_int_count(l, 17) == 2);
	array_int_clear(l);
}

Created methods

The following methods are automatically and properly created by the previous macros. In the following methods, name stands for the name given to the macro that is used to identify the type.

In the following descriptions, it_t is an iterator of the container container_t is the type of the container and type_t is the type of object contained in the container.

void name_find(it_t it, container_t c, const type_t data)

Search for the first occurrence of 'data' within the container. Update the iterator with the found position or return end iterator. The search is linear.

bool name_contains(container_t c, const type_t data)

Return true if 'data' is within the container, false otherwise. The search is linear.

void name_find_last(it_t it, container_t c, const type_t data)

Search for the last occurrence of 'data' within the container. Update the iterator with the found position or return end iterator. The search is linear and can be backward or forwards depending on the possibility of the container.

size_t name_count(container_t c, const type_t data)

Return the number of occurrence of 'data' within the container. The search is linear.

TODO: map, reduce, map_reduce, min, max, minmax, sort_p, uniq, sort add, sub, mul, div, union, intersect

ALGO_MAP(container, oplist, func[, arguments..])

Apply the function 'func' to each element of the container 'container' of oplist 'oplist' :

 for each item in container do
 	 func([arguments,] item)

The function 'func' is a method that takes as argument an object of the container and returns nothing.

ALGO_EXTRACT(containerDest, oplistDest, containerSrc, oplistSrc, func[, arguments..])

Extract the items of the container containerSrc of oplist oplistSrc into the containerDest of oplist oplistDest:

 CLEAN (containerDest)
 for each item in containerSrc do
 	 if func([arguments,] item) 
      	 Push item in containerDest

The function 'func' is a method that takes as argument an object of the container and returns a boolean that is true if the object shall be added to the other container.

ALGO_REDUCE(dest, container, oplist, reduceFunc[, mapFunc[, arguments..])

Reduce the items of the container 'container' of oplist 'oplist' into a single element by applying the reduce function:

 dest = reduceFunc(mapFunc(item[0]), reduceFunc(..., reduceFunc(mapFunc(item[N-2]), mapFunc(item[N-1]))))

'mapFunc' is a method which prototype is:

void mapFunc(dest, item)

with both dest & item that are of the same type than the one of the container. It transforms the 'item' into another form that is suitable for the reduceFunc. If mapFunc is not specified, identity will be used instead.

'reduceFunc' is a method which prototype is:

 void reduceFunc(dest, item)

It integrates the new 'item' into the partial 'sum' 'dest. The reduce function can be the special keywords add, sum, and, or in which case the special function performing a sum/sum/and/or operation will be used.

M-MEMPOOL

This header is for generating specialized optimized memory pools: it will generate specialized functions to alloc & free only one kind of an object. The mempool functions are not thread safe for a given mempool.

MEMPOOL_DEF(name, type)

Generate specialized functions & types prefixed by 'name' to alloc & free a 'type' object.

Example:

MEMPOOL_DEF(mempool_uint, unsigned int)

void f(void) {
      mempool_uint_t m;
      mempool_uint_init(m);
      unsigned int *p = mempool_uint_alloc(m);
      mempool_uint_free(m, p);
      mempool_uint_clear(m);
    }

Created methods

The following methods are automatically and properly created by the previous macros. In the following methods, name stands for the name given to the macro that is used to identify the type.

name_t

The type of a mempool.

void name_init(name_t m)

Initialize the mempool 'm'.

void name_clear(name_t m)

Clear the mempool 'm'. All allocated objects associated to the this mempool that weren't explicitly freed will be deleted too (without calling their clear method).

type *name_alloc(name_t m)

Create a new object of type 'type' and return a new pointer to the uninitialized object.

void name_free(name_t m, type *p)

Free the object 'p' created by the call to name_alloc. The clear method of the type is not called.