Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/ryanbalsdon/libr

A collection of useful, free, single-file libraries for C.
https://github.com/ryanbalsdon/libr

Last synced: about 2 months ago
JSON representation

A collection of useful, free, single-file libraries for C.

Awesome Lists containing this project

README

        

# R Library
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.

# Contributing
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.

# Namespacing Rules
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.

## Namespacing Examples
- `R_Type_New`: `New` is a class method of the `Type` class in the `R` namespace.
- `R_Integer_get`: `get` is an instance method of the `Integer` class in the `R` namespace. It's first argument is an `R_Integer*`.
- `r_type`: This is a local variable who's name doesn't conflict with any namespaces or classes.

# R_Type
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.

## Header Definition
Use the `R_Type_Declare` macro to declare that your struct is a class.
```
typedef struct NewClass NewClass;
R_Type_Declare(NewClass);
```

## Implementation
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;
}
```

## Allocation
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);
```

## Constructors and Destructors
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);
```

## Deep Copying
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);
```

## Duck Typing / Dynamic method calls / Interfaces
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);
```

# R_Type Builtins
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

# R_MutableData
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...

# R_MutableString
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);
```

# R_List
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");
}
```

# R_Dictionary
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));
}
}
```

# R_Events
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);
}
```