This library is a collection of small utilities for C. Most of them revolve around higher-level data management and types because that's where C is most lacking.
Fork this repo, make your change, add any relevant tests then open a Pull Request. The tests can be run with make test
or make valgrind
if Valgrind is installed.
Because Namespacing doesn't exist in C, some general rules are used to achieve it:
- Namespaces and classes are CamelCase, starting with an uppercase.
- Namespaces and classes are separated with an underscore.
- Class methods are CamelCase, starting with an uppercase.
- Instance methods are camelCase, starting with a lowercase. Instance methods always take an object as their first argument.
- Local or instance values are snake_case, starting with a lowercase.
R_Type_New
:New
is a class method of theType
class in theR
namespace.R_Integer_get
:get
is an instance method of theInteger
class in theR
namespace. It's first argument is anR_Integer*
.r_type
: This is a local variable who's name doesn't conflict with any namespaces or classes.
R_Type is an OO (Object-Oriented) framework for C. All the other utilities in libr build off this framework. It uses structs as a blueprint but relies on the first data element of the struct being a pointer to info about its type.
Use the R_Type_Declare
macro to declare that your struct is a class.
typedef struct NewClass NewClass;
R_Type_Declare(NewClass);
Use the R_Type_Define
macro to define a new class using your struct.
R_Type_Define(NewClass, NULL);
The struct must have an R_Type (or void) pointer as its first element.
struct NewClass {
R_Type* isa;
//Other elements of the class can follow
int some_integer;
float floating_point_value;
char* etc;
}
Objects are allocated using the R_Type_New
function and deallocated with R_Type_Delete
.
NewClass* instance = R_Type_New(NewClass);
assert(R_Type_IsOf(instance, NewClass));
R_Type_Delete(instance);
Constructor and Destructor methods can be added to the class in the R_Type_Define
call.
NewClass* NewClass_Constructor(NewClass* self) {return self;}
NewClass* NewClass_Destructor(NewClass* self) {return self;}
R_Type_Define(NewClass, .ctor = NewClass_Constructor, .dtor = NewClass_Destructor);
If your class contains other allocated objects, create a custom copy method. Otherwise, use the builtin shallow copy.
R_Type_Define(NewClass, .copy = R_Type_shallowCopy);
//or...
NewClass* NewClass_Copier(NewClass* old, NewClass* new) {old->value = new->value; return new;}
R_Type_Define(NewClass, .copy = NewClass_Copier);
Use a method table to implement an interface/selector in your class. The last entry in this list must be R_JumpTable_Entry_NULL
. Methods are added above that using the R_JumpTable_Entry_Make
macro.
size_t NewClass_stringify(R_Float* self, char* buffer, size_t size) {
int bytes_written = os_snprintf(buffer, size, "NewClass, in string form");
return bytes_written < size ? bytes_written : size;
}
static R_JumpTable_Entry NewClass_methods[] = {
R_JumpTable_Entry_Make(R_Stringify, NewClass_stringify),
R_JumpTable_Entry_NULL
};
R_Type_Define(NewClass, .interfaces = NewClass_methods);
There are wrapper classes built with R_Type for the standard C types. These are meant to be immutable types but their struct definitions are left public. This is to make optimizations that use the lower-level types possible.
The builtins are:
- R_Type_Integer
- R_Type_Float
- R_Type_Unsigned
- R_Type_Boolean
- R_Type_Null
- R_Type_Data
- R_Type_String
This is a dynamic-sized byte array. It will increase its own allocation as needed.
An example of its usage:
R_MutableData* bytes = R_Type_New(R_MutableData);
R_MutableData_appendBytes(bytes, 0x01, 0x42, 0xFF, 0xa5);
assert(R_MutaData_byte(bytes, 1) == 0x42);
R_Type_Delete(bytes);
It comes with many helper methods to append and extract data like:
- R_MutableData_appendByte
- R_MutableData_appendArray
- R_MutableData_appendHexString
- R_MutableData_pop
- R_MutableData_shift
- etc...
This is a dynamic-sized string that is built off of R_MutableData and is used similarly.
R_MutableString* string = R_MutableString_setString(R_Type_New(R_MutableString), "Hello");
R_MutableString_push(string, ' ');
R_MutableString_appendCString(string, "World");
assert(strcmp(R_MutableString_getString(string), "Hello World") == 0);
R_Type_Delete(string);
This is a list of R_Type instances. The instances' memory is managed by the list. The list can allocate a new instance or being given an existing object to manage.
R_List* strings = R_Type_New(R_List);
R_MutableString* first_string = R_List_add(strings, R_MutableString);
R_MutableString* second_string = R_Type_New(R_MutableString);
R_List_transferOwnership(strings, second_string);
assert(R_List_size(strings) == 2);
R_Type_Delete(strings); //This will deallocate both first_string and second_string!
The list can be looped over with an R_List_each
method.
R_List_each(strings, R_MutableString, string) {
assert(R_Type_IsOf(string, R_MutableString));
}
R_List_each(strings, void, unknown_type) {
if (R_Type_IsOf(unknown_type, R_MutableString)) printf("This is a mutable string!\n");
if (R_Type_IsOf(unknown_type, R_Integer)) printf("This is an integer!\n");
if (R_Type_IsOf(unknown_type, R_Boolean)) printf("This is a boolean!\n");
}
This is a key-value dictionary, implemented as an R_List of R_KeyValuePair
instances. It supports JSON parsing, manual creation and each loops.
R_Dictionary* dictionary = R_Type_New(R_Dictionary);
R_Integer_set(R_Dictionary_add(dictionary, "key1", R_Integer), 1);
R_Integer_set(R_Dictionary_add(dictionary, "key2", R_Integer), 2);
assert(R_Dictionary_size(dictionary) == 2);
R_Type_Delete(dictionary);
Each loop returns an R_KeyValuePair instance which has key and value getters.
R_Dictionary_each(dictionary, element) {
if (R_MutableString_compare(R_KeyValuePair_key(element), "key1")) {
printf("Found 'key1': ");
R_Puts(R_KeyValuePair_value(element));
}
}
This a Event/Notification/Actor Model system using callbacks and implemented using R_Dictionary.
void test_callback(void* target, const char* event_key, void* payload) {printf("Notified!");}
void main(void) {
R_Events* notification_center = R_Type_New(R_Events);
R_Events_register(notification_center, "Event Name", NULL, test_callback);
R_Events_notify(notification_center, "Event Name", NULL)
R_Type_Delete(notification_center);
}