Intro

A basic attempt to add "objects" to C programs, loosely inspired in C++ and Java but restricted to the C standard, considering:

  1. Compile-time type safety

  2. Objects look like common variables: no pointer syntax, or pointer de-referencing

  3. Classes may implement multiple interfaces, but don’t extend from other classes

  4. Interfaces may extend from other interfaces

  5. Object and Interface "references" casting conversion

  6. Objects are allocated from the heap but provisions are made to use the stack

  7. Manual memory management (no GC/smart pointer/etc.)

OC does not provide class access scopes (all is public), nor static attributes/methods, nor method overloading. Internally, classes and interfaces are translated into C structs with several auxiliary members (which the developer may ignore.)

Warning
OC does require a (modern) C compiler which supports the VA_OPT() standard macro.

Using OC

Declaring types

Consider a class CLS1 which implements the interfaces IFACE1 and IFACE2. This specification must be written in a "header file" of any name. For our first example we’ll use basic-def.h:

/*
 * basic-def.h: types definition for OC
 */

INTERFACE(IFACE1,
	METHOD(fun1, int, (double, int))
	METHOD(fun2, long, (int, int))
	METHOD(fun3, IFACE2, (int, IFACE1))
	,
)
INTERFACE(IFACE2,
	METHOD(fun2, long, (int, int))
	METHOD(fun4, int, (int, double))
	,
)

CLASS(CLS1,
        ATTR(int a)
        ATTR(int b)
        ATTR(double c)
        ATTR(double d)
        ,
        METHOD(fun5, int, (CLS1, int))
        ,
        IMPLEMENTS(IFACE1)
        IMPLEMENTS(IFACE2)
)
Note
For bigger projects, the developer may distribute the type definitions in several "header files" which must be #included from basic-def.h.

The syntax should be obvious: METHOD() declares class and interface methods (member functions), providing three arguments: the member’s name, the return type, and the arguments (which must be enclosed by parenthesis.)

The super-types must appear before the sub-types; this rule applies for both interfaces and classes.

Note that CLS1 inherits the fun2() method from its two implemented interfaces; the fun2() declaration MUST be the same in both interfaces, since we do not support method overloading of any kind.

As shown, interfaces do require a name, a number of methods, and (see below) the name of the parent (to be extended) interfaces; the classes do require a name, a number of attributes, more member functions, and the implemented interface names. Besides the type name, all the just mentioned arguments could be empty.

Now, the ocgen tool must be executed, providing:

  1. The developer-provided OC definitions header file as the one shown above

  2. A converted plain C header file to be generated with the corresponding type definitions

  3. A C-languange "OC helper library" to be generated

  4. A "skeleton" for the implementation of the declared methods, to be generated

The three output files may have any name, but here we’ll be consistent and use basic-types.h, basic-oclib.c and basic-impl.c.skel:

./ocgen basic-def.h basic-types.h basic-oclib.c basic-impl.c.skel

The result is a second-stage generator customized for the basic types, named custom-ocgen.c which must be compiled and executed:

cc -o custom-ocgen custom-ocgen.c
./custom-ocgen

This custom generator will create the three output files mentioned above.

Warning
All the generated files will be totally overwritten when the generator is re-executed, so avoid editing them.

Implementing Methods

In another file (which for our example we’ll call basic-impl.c), the developer must implement the declared class and interface methods. The generated "implementation skeleton" may be used as a starting point for this task. For example:

#include "basic-types.h"

CTOR(CLS1) {
	SETUP_THIS(CLS1);
	this->a = 34;
	this->b = 1;
	return this;
}

DTOR(CLS1) {
	printf("Destructor running: a=%d\n",this->a);
}

int CMETHOD(CLS1,fun1,double arg1, int arg2){
	SETUP_THIS(CLS1);
	this->b ++;
	return 4 + arg2 + this->a;
}

long CMETHOD(CLS1,fun2,int a, int b){
	return 1L;
}

IFACE2 CMETHOD(CLS1,fun3,int arg1, IFACE1 arg2){
	CLS1 o1 = NEW(CLS1);
	o1->a = 6966 + arg1;
	o1->b = 2001;
	IFACE2 i2 = GET_REF(IFACE2, o1);
	return i2;
}

int CMETHOD(CLS1,fun4,int arg1, double arg2){
	SETUP_THIS(CLS1);
	this->a *= 2;
	return 1000 - arg1;
}

int CMETHOD(CLS1, fun5, CLS1 arg1, int arg2) {
	SETUP_THIS(CLS1);
	return this->a + arg1->a + 2*arg2;
}

int main(int argc, char **argv) {
	CLS1 o1 = NEW(CLS1);
	printf("a=%d b=%d\n", o1->a, o1->b);
	int r = CALL(o1, fun1, 31.66, 10);
	printf("a=%d b=%d r=%d\n", o1->a, o1->b, r);

	IFACE1 i = GET_REF(IFACE1, o1);
	int r2 = CALL(i, fun1, 333.11, 20);
	printf("a=%d b=%d r=%d\n", o1->a, o1->b, r2);

	IFACE2 i2 = GET_REF(IFACE2,o1);
	int r3 = CALL(i2, fun4, 11, 20.0);
	printf("a=%d b=%d r=%d\n", o1->a, o1->b, r3);

	IFACE2 i2x = CALL(o1, fun3, -1, i);
	CLS1 or1 = CAST(CLS1, i2x);
	if(or1 == NULL) {
		fprintf(stderr, "Can't cast i2x->CLS1\n");
		return 1;
	}
	return 0;
}

The SETUP_THIS(class-name) macro is needed to access the this variable in the class methods. CTOR(class-name) and DTOR(class-name) are the constructor and destructor, respectivelly. Those are mandatory but may remain empty. Note that CTOR() must return this.

As usual, a main() function is required. It was added at the end of the just shown file, but could be placed in any other one.

Warning
OC creates a number of global identifiers prefixed with OC_ and oc_. The application should avoid the creation of identifiers such prefixes in order to prevent name conflicts.

Building

Finally, the user implementation basic-impl.c and the auxiliary library basic-oclib.c must be compiled:

cc -o basic basic-impl.c basic-oclib.c
./basic
Important
Any other participating C file must include the basic-types.h header file. This is the only requirement in order to access the OC declared types.

Using Objects

Objects of the declared classes are created with the NEW() macro. The object will be allocated in the heap (under the hood with calloc()); later, the object should be destroyed with the DELETE() macro.

It is safe to call DELETE() with a NULL argument (nothing happens.)

Note
The DELETE() macro does not set the object to NULL at the end. Be careful of duplicated calls to DELETE() with invalid pointers.
Note
It is guaranteed that the object will be zero-initialized (from the internal usage of calloc()) before the constructor call.

Methods are executed with the CALL() macro, which requires the object variable, the method’s name, and the arguments.

Note that the CALL() invocations will be type-checked by the compiler.

CLS1 o1 = NEW(CLS1);
int r = CALL(o1, fun1, 31.66, 10);

The equivalent C++/Java notation would be o1.fun1(31.66,10).

As a limitation the language, objects must be explicitly converted to their corresponding interface types in order to call interface-declared methods. Casting may return NULL if unable to convert:

IFACE1 i = CAST(IFACE1, o1);
if(i == NULL) {
	fprintf(stderr, "Can't convert\n");
}
int r2 = CALL(i, fun1, 333.11, 20);

Note that i works as another name for the same object (is a "reference".)

Important
The meaning of "reference" in OC is just a variable which works like an alias for another one, but which conforms to some implemented interface of the class. It does not imply any "borrow" semantics, also:
Warning
Deleting on a reference has the same effect a deleting on an object. In the current example, using DELETE(i) has the same effect as DELETE(o1).

When we have a "concrete" object which must be converted to any of its implementing interfaces, the GET_REF() macro is a bit faster and forces a compile-time type check:

IFACE1 i = GET_REF(IFACE1, o1);
int r2 = CALL(i, fun1, 333.11, 20);

From an interface typed reference, the "real" object (or a reference to another implemented interface) may be obtained with a "cast":

CLS1 or1 = CAST(CLS1, i2x);
if(or1 == NULL) {
	fprintf(stderr, "Can't convert\n");
}

The CAST() operation does return NULL if the conversion is not possible; it works like dynamic_cast from C++.

More on Constructors

Here we present a (very dummy) implementation for a "String" class:

/*
 * string-def.h: types definition for OC
 */

CLASS(STRING,
        ATTR(char *str)
	,
        CTOR(STRING, (const char *data))
        METHOD(set, void, (const char *ptr))
        METHOD(add, void, (const char *ptr))
        METHOD(get, const char *, ())
	,
        IMPLEMENTS(ID_INTERFACE)
)

Note that the constructor does receive a single argument. When no constructor is provided, a default no-argument one is declared (which must be implemented by the user.) Since we do not support method overloading, only a single constructor is allowed per class.

Also, our STRING class implements an interface named ID_INTEFACE. This interface is automatically declared by OC, and is defined as:

INTERFACE(ID_INTERFACE,
	METHOD(equals,int,(void *))
	METHOD(hashcode,long,())
,)

Now we show a possible implementation for STRING:

#include "str-types.h"
#include <string.h>
#include <stdlib.h>

/* class STRING */

CTOR(STRING, const char *data) {
	SETUP_THIS(STRING);
	this->str = strdup(data);
	return this;
}

DTOR(STRING) {
	free(this->str);
}

int CMETHOD(STRING, equals, void *obj) {
	SETUP_THIS(STRING);
	STRING str = CAST(STRING, obj);
	if(str == NULL) return 0;
	return strcmp(this->str, str->str)==0;
}

long CMETHOD(STRING, hashcode) {
	SETUP_THIS(STRING);
	int len = strlen(this->str);
	long h = 0;
	for (int i = 0; i < len; i++) {
		h = 31*h + this->str[i];
	}
	return h;
}

void CMETHOD(STRING, set, const char *ptr) {
	SETUP_THIS(STRING);
	if(ptr == NULL) abort();
	free(this->str);
	this->str = strdup(ptr);
}

void CMETHOD(STRING, add, const char *ptr) {
	SETUP_THIS(STRING);
	if(ptr == NULL) abort();
	this->str = realloc(this->str, strlen(this->str) + strlen(ptr) + 1);
	strcat(this->str, ptr);
}

const char *CMETHOD(STRING, get) {
	SETUP_THIS(STRING);
	return this->str;
}

int main(int argc, char **argv) {
	STRING s = NEW(STRING, "Hello");
	CALL(s, add, " ");
	CALL(s, add, "World!");
	printf("String->%s\n", CALL(s, get));
	return 0;
}

which at the end just shows a traditional 'Hello World'.

Using the stack

For ephemeral objects the stack may be a convenient (faster) option. Stack objects are automatically freed (but not destroyed) when their enclosing function ends its execution, so the DELETE() macro should always be in place for the correct invocation of the object’s destructor method.

Note
DELETE() actually may be avoided if the destructor is empty. Safer code should call it anyway since future versions of the class may add code to the destructor.

Stack objects are built with the INIT_STACK(class-name, variable-name, …​) macro, which like its counterpart NEW() is in charge of constuctor invocation. Let’s show an example, as usual starting with the type definition, taking note of a two-arguments constructor:

/*
 * stack-def.h: types definition for OC
 */

CLASS(CLS1,
        ATTR(int a)
        ATTR(int b)
        ,
        METHOD(sum, int, ())
        CTOR(CLS1, (int, int))
        ,
)

The variable identifier provided as second argument to INIT_STACK() is a concrete object whose address must be taken when calling the usual OC macros (using the & prefix operator); also, the access to the object attributes is made via the dot operator (object.attribute.) Compare the usage of a normal (heap allocated) object o1 with the stack version o2:

#include "stack-types.h"

CTOR(CLS1, int arg1, int arg2) {
	SETUP_THIS(CLS1);
	this->a = arg1;
	this->b = arg2;
	return this;
}

DTOR(CLS1) {
	printf("Dtor is called\n");
}

int CMETHOD(CLS1,sum) {
	SETUP_THIS(CLS1);
	return this->a + this->b;
}

int main(int argc, char **argv) {
	CLS1 o1 = NEW(CLS1, 20, 30);
	int r1 = CALL(o1, sum);
	printf("a=%d b=%d r=%d\n", o1->a, o1->b, r1);
	DELETE(o1);
	INIT_STACK(CLS1, o2, 25, 35);
	int r2 = CALL(&o2, sum);
	printf("a=%d b=%d r=%d\n", o2.a, o2.b, r2);
	DELETE(&o2);
	return 0;
}

As a general rule, use stack objects for very-ephemeral ones.

Note
Like it’s NEW() counterpart, the INIT_STACK() creates a zero-initialized object (via memset().)

Lists

Lists are object containers which allow the sequential insertion of elements of a selected type (OC objects.) OC provides two implementations: ARRAYLIST and LINKEDLIST.

ARRAYLIST is similar to the C++ vector and Java’s ArrayList, with a fast element access by position. LINKEDLIST is a linked list, which allows a fast removal of intermediate elements.

List classes are generated per contained object’s class; given a class T, a LIST(T) macro call must be placed in the definitions file. This does create an interface type also named LIST(T) and the classes ARRAYLIST(T) and LINKEDLIST(T).

/*
 * alist-def.h: types definition for OC
 */

CLASS(CLS1,
        ATTR(int a)
        ATTR(int b)
        ,
        ,
)

LIST(CLS1)

Now, we may define use such lists like in the following implementation:

#include "alist-types.h"

/* class CLS1 */

CTOR(CLS1) {
	SETUP_THIS(CLS1);
	this->a = 34;
	this->b = 1;
	return this;
}

DTOR(CLS1) {
}

int main(int argc, char **argv) {
	CLS1 o1 = NEW(CLS1); o1->a = 10;
	CLS1 o2 = NEW(CLS1); o2->a = 20;
	CLS1 o3 = NEW(CLS1); o3->a = 30;
	ARRAYLIST(CLS1) list1 = NEW(ARRAYLIST(CLS1));
	LIST(CLS1) list1r = GET_REF(LIST(CLS1), list1);
	LIST_ADD(list1r, o1, OC_OWN_NONE);
	LIST_ADD(list1, o2, OC_OWN_NONE);
	LIST_ADD(list1, o3, OC_OWN_NONE);
	printf("List size=%d\n", LIST_SIZE(list1));
	int z;
	for(z = 0 ; z < LIST_SIZE(list1) ; z ++) {
		CLS1 o = LIST_GET(list1, z);
		printf("List element %d -> .a=%d .b=%d\n",
			z, o->a, o->b);
	}
	ITERATOR(CLS1) it = GET_ITERATOR(list1);
	while(CALL(it,has_next)) {
		CLS1 obj = CALL(it,next);
		printf("List element from iter .a=%d .b=%d\n",
			obj->a, obj->b);
	}
	DELETE(it);

	printf("before delete Total refs = %ld\n",
			oc_get_total_objects());
	DELETE(list1);
	printf("after delete Total refs = %ld\n",
			oc_get_total_objects());
	return 0;
}

As shown, elements may be added to the list with the LIST_ADD() macro, and extracted with LIST_GET(). The arguments for LIST_ADD() are the list, the object to be added, and the ownership. The ownership simply means whether the list is in charge of DELETING the contained object when it (the list) is cleared or deleted (constant OC_OWN_ELEMENT) or is calling code is in charge (constant OC_OWN_NONE.) Note that LIST_REMOVE() removes the element from the list, but does not delete the just extracted element (effectively this operation "rescues" it.) In the previous example, the elements are inserted without ownership (OC_OWN_NONE is passed to LIST_ADD()), so the result is as shown:

$ ./alist
List size=3
List element 0 -> .a=10 .b=1
List element 1 -> .a=20 .b=1
List element 2 -> .a=30 .b=1
List element from iter .a=10 .b=1
List element from iter .a=20 .b=1
List element from iter .a=30 .b=1
before delete Total refs = 4
after delete Total refs = 3

This example also shows two ways of traversing the list elements: by position and by iterator. For any defined list type, a new "iteration type" is automatically defined with the type ITERATOR(T).

Warning
Traversing elements by position in long linked lists is very slow: use an iterator for that case.
Note
The last example shows the usage of the oc_get_total_objects() global function which returns the total number of created objects (excluding those in the stack.) It may be useful to catch memory leaks originated from untracked objects.

Now compare the ownership with the following example:

#include "llist-types.h"

/* class CLS1 */

CTOR(CLS1) {
	SETUP_THIS(CLS1);
	return this;
}

DTOR(CLS1) {
}

int main(int argc, char **argv) {
	int z;
	LINKEDLIST(CLS1) list1 = NEW(LINKEDLIST(CLS1));
	for(z = 0 ; z < 1000 ; z ++) {
		CLS1 o1 = NEW(CLS1); o1->a = z;
		LIST_ADD(list1, o1, OC_OWN_ELEMENT);
	}
	printf("List size=%d\n", LIST_SIZE(list1));
	printf("before delete Total refs = %ld\n",
			oc_get_total_objects());
	DELETE(list1);
	printf("after delete Total refs = %ld\n",
			oc_get_total_objects());
	return 0;
}

whose output is:

$ ./llist
List size=1000
before delete Total refs = 1001
after delete Total refs = 0
Warning
Referencing a list element out of range is currently undefined: always check the list limits if unsure. This behavior may change in the future.

If a critical section (called many times) does require making an iteration, it could be convenient to avoid the heap allocation (of the iterator) and use a stack version as shown next:

#include "alist2-types.h"

/* class CLS1 */

CTOR(CLS1) { SETUP_THIS(CLS1); return this; }
DTOR(CLS1) { }

int main(int argc, char **argv) {
	CLS1 o1 = NEW(CLS1); o1->a = 10;
	CLS1 o2 = NEW(CLS1); o2->a = 20;
	CLS1 o3 = NEW(CLS1); o3->a = 30;
	ARRAYLIST(CLS1) list1 = NEW(ARRAYLIST(CLS1));
	LIST_ADD(list1, o1, OC_OWN_NONE);
	LIST_ADD(list1, o2, OC_OWN_NONE);
	LIST_ADD(list1, o3, OC_OWN_NONE);
	INIT_STACK(ITERATOR(CLS1), it);
	CALL(list1, iterator, &it); /* setup iterator */
	while(CALL(&it,has_next)) {
		CLS1 obj = CALL(&it,next);
		printf("List element from iter .a=%d .b=%d\n",
			obj->a, obj->b);
	}
	DELETE(&it); /* really not necessary */
	DELETE(list1);
	return 0;
}

Sorting lists

Lists provide a "quicksort" implementation. For example:

int my_comparator(CLS1 o1, CLS1 o2) {
	if(o1->a == o2->a) return 0;
	return (o1->a < o2->a)?-1:1;
}
...
CALL(list1, sort, my_comparator);
}

This will sort the list in-situ, using the comparator function provided by the user. Following the usual conventions of C’s qsort, the comparator function must return -1, o or 1 if the parameters satisfy the <, = or > relations respectivelly.

Note that this is targeted for ARRAYLIST; on a LINKEDLIST the implementation will create a temporary ARRAYLIST, sort it, and recreate the LINKEDLIST, so this is very inefficient.

Maps

Also known as dictionaries, hashtables, and associative arrays, are object containers which allow the insertion of key/value entries, providing a convenient query of the elements by their key. Currently OC only support the HASHMAP implementation, which allows for a fast query by key.

In order to use maps, a MAP(key-class,value-class) declaration must be inserted into the definitions header file.

Hash maps are created with a macro call like NEW(HASHMAP(K,V)). For for every map, a "support entry class" may also be created with the ENTRY() macro, whose elements are key, value and ownership. Maps may be considered an storage for such "entries".

Important
The key objects must implement the built-in ID_INTERFACE.
/*
 * hash1-def.h: types definition for OC
 */

CLASS(CLS1,
        ATTR(int a)
        ,
        ,
)

CLASS(STRING,
        ATTR(char *str)
	,
        CTOR(STRING, (const char *data))
        METHOD(set, void, (const char *ptr))
        METHOD(add, void, (const char *ptr))
        METHOD(get, const char *, ())
	,
        IMPLEMENTS(ID_INTERFACE)
)

MAP(STRING, CLS1)

Care must be provided to the ownership of the entry’s members. Since the entries may be fully removed or replaced by the provided operations, the developer is in charge of the correct deletion of no longer needed objects.

The following example shows its usage (the STRING class implementation was shown in previous listings):

#include "hash1-types.h"
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv) {
	HASHMAP(STRING, CLS1) h1 = NEW(HASHMAP(STRING, CLS1));
	int z;
	ENTRY(STRING, CLS1) fentry = NEW(ENTRY(STRING, CLS1));
	/* put some entries in the hashtable */
	for(z = 1 ; z <= 20 ; z ++) {
		char *buf = malloc(10);
		sprintf(buf, "k%d", z);
		STRING s = NEW(STRING, buf);
		free(buf);
		CLS1 obj = NEW(CLS1); obj->a = z * 5;
		fentry->key = s; fentry->value = obj; fentry->ownership = OC_OWN_NONE;
		MAP_PUT(h1, fentry);
	}
	/* get the entries in the hashtable */
	for(z = 1 ; z <= 20 ; z ++) {
		char *buf = malloc(10);
		sprintf(buf, "k%d", z); /* this must exist */
		STRING s = NEW(STRING, buf);
		CLS1 obj;
		if(!MAP_GET(h1, s, fentry)) abort();
		obj = fentry->value;
		if(obj == NULL || obj->a != z * 5) abort();
		sprintf(buf, "r%d", z); /* this not */
		STRING n = NEW(STRING, buf);
		if(MAP_GET(h1, n, fentry)) abort();
	}
	/* removing a value */
        printf("size before removing: %d\n", MAP_SIZE(h1));
        STRING key5 = NEW(STRING, "k5");
        if(!MAP_REMOVE(h1, key5, fentry)) {
		fprintf(stderr, "Element k5 was not found\n");
		abort();
	}
	CLS1 v5 = fentry->value;
        printf("removed value=%d\n", v5->a);
        DELETE(v5);
        printf("size after removing: %d\n", MAP_SIZE(h1));
	/* the element should no longer exist in the hashtable */
        if(MAP_GET(h1, key5, fentry)) {
		fprintf(stderr, "Element was not removed\n");
		abort();
	}
	DELETE(fentry);
	DELETE(key5);
	DELETE(h1);
	return 0;
}

As shown, MAP_PUT inserts an entry in the table; if an existing entry is being replaced, then the macro returns 1, and the previous key/value entry is returned in the same entry object which was provided for the new entry.

The ownership has the same semantics as for lists, but it is managed independently for the key objects (constant OC_OWN_KEY) and value objects (constant OC_OWN_VALUE.) Both may be or-ed, and the same effect may be achieved with the constant OC_OWN_BOTH.

The user is in charge of creating the entry objects, which are never "captured" by the container, so they may be created in the stack as shown in the previous example.

When no replacement is done (a simple insertion), MAP_PUT does return zero, and the entry contents are nullified.

Similarly, MAP_GET retrieves an entry from a provided key. If found, returns 1 and the provided entry is filled. Else, just returns zero and the entry is not touched.

In the same vein, MAP_REMOVE works like MAP_GET but removes the entry (from the hash table) when found.

MAP_REMOVE may accept a NULL pointer for the entry, when there is no interest in the removed contents.

Iteration

Besides entry support classes, an iterator class is automatically created which allows for traversing the hashtable elements. The following example also shows the usage of a stack object for the entries:

#include "hash2-types.h"
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv) {
	HASHMAP(STRING,CLS1) h1 = NEW(HASHMAP(STRING,CLS1));
	INIT_STACK(ENTRY(STRING,CLS1), fentry);
	int z;
	for(z = 1 ; z <= 10 ; z ++) {
		char *buf = malloc(10);
		sprintf(buf, "k%d", z);
		STRING s = NEW(STRING, buf);
		free(buf);
		CLS1 obj = NEW(CLS1); obj->a = z * 5;
		fentry.key = s; fentry.value = obj; fentry.ownership = 0;
		MAP_PUT(h1, &fentry);
	}
	ITERATOR(ENTRY(STRING,CLS1)) it = GET_ITERATOR(h1);
        while(CALL(it, has_next)) {
                ENTRY(STRING,CLS1) entry = CALL(it, next);
                CLS1 obj = entry->value;
                printf("Content from iterator -> %d\n", obj->a);
        }
	DELETE(it);
	DELETE(h1);
	return 0;
}

Sets

Unordered containers for elements which implement the internal ID_INTERFACE. Currenty the only implementation is HASHSET.

/*
 * hset1-def.h: types definition for OC
 */

CLASS(STRING,
        ATTR(char *str)
	,
        CTOR(STRING, (const char *data))
        METHOD(set, void, (const char *ptr))
        METHOD(add, void, (const char *ptr))
        METHOD(get, const char *, ())
	,
        IMPLEMENTS(ID_INTERFACE)
)

SET(STRING)

Access to its elements must be done by iteration:

#include "hset1-types.h"
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv) {
	HASHSET(STRING) h1 = NEW(HASHSET(STRING));
	int z;
	/* put some entries in the hashtable */
	for(z = 1 ; z <= 20 ; z ++) {
		char *buf = malloc(10);
		sprintf(buf, "k%d", z);
		STRING s = NEW(STRING, buf);
		free(buf);
		CALL(h1, add, s, OC_OWN_ELEMENT);
	}
	ITERATOR(STRING) it = GET_ITERATOR(h1);
        while(CALL(it, has_next)) {
		STRING e = CALL(it, next);
		printf("Iterable -> %s\n", e->str);
	}
	DELETE(it);
	DELETE(h1);
	return 0;
}

Container Library

The containers' implementation is exposed for direct usage:

/* get the total number of extant objects in the heap (stack located ones are not counted.) */
extern long oc_get_total_objects(void);

extern const char *oc_get_registry_interface_name(int pos);
extern const char *oc_get_registry_class_name(int pos);
extern int oc_get_registry_interface_size(void);
extern int oc_get_registry_class_size(void);
extern long oc_get_registry_class_count(int pos);
extern int oc_get_registry_interface_index(const char *name);
extern int oc_get_registry_class_index(const char *name);

/*** membership constants ***/

/* when adding to a container, do not own the element */
#define OC_OWN_NONE 0

/* when adding to a list, own the element */
#define OC_OWN_ELEMENT 1

/* when adding to a table, own the key */
#define OC_OWN_KEY 2

/* when adding to a table, own the value */
#define OC_OWN_VALUE 4

/* when adding to a table, own the key and value */
#define OC_OWN_BOTH 6

/*** iterator support structure ***/
typedef struct OC_ITERATOR {
	void *list;
	int pos; void *block; void *aux; char ltype;
} OC_ITERATOR;

/* iterator has next element? */
bool oc_iterator_has_next(OC_ITERATOR *);

/* get next element from iterator and go to the next position */
void *oc_iterator_next(OC_ITERATOR *, char *);

/*** lists ****/

/* create a new linked list; returns NULL when out of memory */
OC_LIST *oc_linkedlist_new(void);

/* create a new array list; returns NULL when out of memory */
OC_LIST *oc_arraylist_new(void);

/* setup iterator */
void oc_list_iterator(OC_LIST *list, OC_ITERATOR *);

/* adds an element at the end of the list */
void oc_list_add(OC_LIST *list, void *data, char ownership);

/* adds an element into the list, pushing the following elements one position */
void oc_list_insert(OC_LIST *list, int pos, void *data, char ownership);

/* gets an element from the list */
void *oc_list_get(OC_LIST *list, int pos);

/* removes all elements in the list */
void oc_list_clear(OC_LIST *list);

/* add all the elements from the second list into the first; the first list is untouched; the
 * inserted elements are not owned by the fist list */
bool oc_list_add_all(OC_LIST *list_dst, OC_LIST *list_src);

/* gets the list size */
int oc_list_size(OC_LIST *list);

/* removes an element in the list */
void *oc_list_remove(OC_LIST *list, int pos);

/* drops the list */
void oc_list_delete(OC_LIST *list);

/* sort the list; currently only for arraylist */
void oc_list_sort(OC_LIST *list, int (*compar)(const void *, const void *, void *), void *);

/* get the maximum element of a list */
void *oc_list_max(OC_LIST *list, int (*compar)(const void *, const void *, void *), void *arg);

/* get the minimum element of a list */
void *oc_list_min(OC_LIST *list, int (*compar)(const void *, const void *, void *), void *arg);

/* generic quicksort, following the interface of GNU qsort_r */
void oc_qsort(void* array, int num, int elen, int (*compar)(const void *, const void *, void *), void *arg);

/*** hashmap ***/

/* support structure to deal with entries */
typedef struct OC_ENTRY {
	char ownership;
	void *key;
	void *value;
} OC_ENTRY;

/* create a new hashmap; returns NULL if out of memory */
OC_HASHMAP *oc_hashmap_new(void);

/* put a new entry in the hashmap, returning whether a replacement was made. In the
 * last case, the entry parameter is updated with the replaced entry */
bool oc_hashmap_put(OC_HASHMAP *hashmap, OC_ENTRY *entry);

/* tries to return the entry with the provided key. Returns whether it was
 * found and the entry parameter is updated with the found entry (else is untouched.) */
bool oc_hashmap_get(OC_HASHMAP *hashmap, void *key, OC_ENTRY *answer);

/* like oc_hashmap_get() but also removes the entry from the hashmap */
bool oc_hashmap_remove(OC_HASHMAP *hashmap, void *key, OC_ENTRY *removed);

/* get the number of entries in the hashmap */
int oc_hashmap_size(OC_HASHMAP *hashmap);

/* test whether there is an entry with the provided key */
bool oc_hashmap_haskey(OC_HASHMAP *hashmap, void *key);

/* deletes the hashmap and its owned objects */
void oc_hashmap_delete(OC_HASHMAP *hashmap);

/* setup iterator */
void oc_hashmap_iterator(OC_HASHMAP *hashmap, OC_ITERATOR *);

/*** hashset ***/

/* create a new hashset; returns NULL if out of memory */
OC_HASHSET *oc_hashset_new(void);

/* get the number of entries in the hashset */
int oc_hashset_size(OC_HASHSET *hashset);

/* put an element in the hashset, returning whether an insertion was made. If
 * the element is already in the set, returns false and the object is not updated
 * with the provided object */
bool oc_hashset_add(OC_HASHSET *hashset, void *element, char ownership);

/* element is contained? */
bool oc_hashset_contains(OC_HASHSET *hashset, void *element);

/* setup iterator */
void oc_hashset_iterator(OC_HASHSET *hashset, OC_ITERATOR *);

/* deletes the hashset and its owned objects */
void oc_hashset_delete(OC_HASHSET *hashset);