mirror of
https://github.com/sigmasternchen/crap-libs
synced 2025-03-15 07:38:56 +00:00
172 lines
5.4 KiB
Markdown
172 lines
5.4 KiB
Markdown
![]() |
# CRAP-Libs
|
||
|
## C RApid Programming Libraries
|
||
|
|
||
|
### What is this sh*t?
|
||
|
Some C-libraries that provide stuff like OOP or try-catch-constructions.
|
||
|
|
||
|
## Wait... OOP in C? You mean C++, right?
|
||
|
Nope. Thanks to structs and preprocessor macros I managed to create a Java-like object-oriented environment with classes, superclasses and even interfaces (although interfaces are merely shortcuts for declaring all methods in the interface by hand).
|
||
|
|
||
|
## Wow. Does that work on every system with every compiler?
|
||
|
Probably not. I used the GCC-specific function attributes and the pragma function to implement things like multiple catch blocks (I had to keep track of which classes are defined.) or interfaces which are not instance-able.
|
||
|
|
||
|
### So, how can I use this... thing?
|
||
|
## oop.h
|
||
|
Well, using (defined) classes is really easy:
|
||
|
|
||
|
```c
|
||
|
LinkedList_t list = new (LinkedList)();
|
||
|
list->add(list, "Hello World");
|
||
|
list->get(list, 0);
|
||
|
list->destruct(list);
|
||
|
```
|
||
|
|
||
|
The first parameter of every method call has to be the object itself. (Because the methods are basically function pointers which don't know where the original object is.)
|
||
|
|
||
|
Each object should be destructed if it's no longer used. (I am going to implement a basic garbage collector someday.)
|
||
|
|
||
|
Note the parentheses around the class name in the first line. I once had a version in which they were not necessary, but unfortunately there was something that prevented me from keeping that feature: C
|
||
|
|
||
|
(For interested fellows: The `construct(class)` macro has to be called in order to construct an object. This macro evaluates to the name of the construct method of that class. Furthermore each class defined a 0-ary macro with the name of the class (e.g. `LinkedList`) which evaluated to `construct(class)`. But that approach handicapped debugging, because on errors (and some warnings) the class name macro gets evaluated in contexts it should not, e.g. function arguments.)
|
||
|
|
||
|
|
||
|
Defining classes is a bit more tricky.
|
||
|
|
||
|
First the header file:
|
||
|
```c
|
||
|
// The following line registers the class.
|
||
|
extern class(TestClass, Object_class, (interfaces) {1 namely {Cloneable_class} }, true) {
|
||
|
// The name of the class is TestClass, it extends Object_class
|
||
|
// and has one interface (Cloneable_class).
|
||
|
// The last parameter (true) indicates that this class is instanceable.
|
||
|
|
||
|
// Now let's define the datastructure.
|
||
|
// First (!) we need the super-object.
|
||
|
extends(Object_t);
|
||
|
|
||
|
// Next we add the definitions from the interface.
|
||
|
implements(Cloneable);
|
||
|
|
||
|
// At last the fields and methods of the class itself:
|
||
|
|
||
|
const char* string;
|
||
|
|
||
|
void (*set)(defclass TestClass*, const char* string);
|
||
|
const char* (*get)(defclass TestClass*);
|
||
|
|
||
|
void (*destruct)(defclass TestClass*);
|
||
|
}
|
||
|
|
||
|
// The following methods need to be "static":
|
||
|
|
||
|
// the constructor
|
||
|
TestClass_t* method(TestClass, construct)(void);
|
||
|
|
||
|
// the populate method (see source file)
|
||
|
void method(TestClass, populate)(TestClass_t*, class_t);
|
||
|
```
|
||
|
|
||
|
The source file:
|
||
|
```c
|
||
|
// The class itself should be defined in the source file.
|
||
|
// This is why in the header file the class is defined extern.
|
||
|
class_t TestClass_class;
|
||
|
|
||
|
// Let's define the constructor.
|
||
|
TestClass_t* method(TestClass, construct)(void) {
|
||
|
throws(OutOfMemoryException_t);
|
||
|
// This method might throw a exception. See above.
|
||
|
|
||
|
|
||
|
// We now have to allocate the memory for the object.
|
||
|
// On how this works: See later (try.h).
|
||
|
sr_(TestClass_t* test = allocate_object(TestClass), NULL);
|
||
|
|
||
|
// Call populate of TestClass on the object.
|
||
|
populate(TestClass)(test, TestClass_class);
|
||
|
|
||
|
return test;
|
||
|
}
|
||
|
|
||
|
// The clone method inherited from Cloneable.
|
||
|
// You might have to look up the interface definitions.
|
||
|
void* method(TestClass, clone)(void* this) {
|
||
|
throws(IllegalArgumentException_t, NullPointerException_t, OutOfMemoryException_t);
|
||
|
|
||
|
// Check for NULL.
|
||
|
if (this == NULL) {
|
||
|
throwr(new (NullPointerException)(), NULL);
|
||
|
}
|
||
|
|
||
|
// Check for class type.
|
||
|
// This will not fail if `this` is an object of an child class of TestClass.
|
||
|
if (!instanceof(this, TestClass_class)) {
|
||
|
throwr(new (IllegalArgumentException)("Argument is not of type TestClass."), NULL)
|
||
|
}
|
||
|
|
||
|
TestClass_t* test = (TestClass_t*) this;
|
||
|
|
||
|
sr_(TestClass_t* clone = new (TestClass)(), NULL);
|
||
|
|
||
|
clone->string = test->string;
|
||
|
|
||
|
return clone;
|
||
|
}
|
||
|
|
||
|
// The following two methods are trivial.
|
||
|
void method(TestClass, set)(TestClass_t* this, const char* string) {
|
||
|
throws(NullPointerException_t);
|
||
|
|
||
|
if (this == NULL) {
|
||
|
throw(new (NullPointerException)());
|
||
|
}
|
||
|
|
||
|
this->string = string;
|
||
|
}
|
||
|
const char* method(TestClass, get)(TestClass_t* this) {
|
||
|
throws(NullPointerException_t);
|
||
|
|
||
|
if (this == NULL) {
|
||
|
throw(new (NullPointerException)());
|
||
|
}
|
||
|
|
||
|
return this->string;
|
||
|
}
|
||
|
|
||
|
// The destructor:
|
||
|
void method(TestClass, destruct)(TestClass_t* this) {
|
||
|
throws(NullPointerException_t);
|
||
|
|
||
|
if (this == NULL) {
|
||
|
throw(new (NullPointerException)());
|
||
|
}
|
||
|
|
||
|
// We don't have anything to destruct.
|
||
|
|
||
|
// Call super destructor.
|
||
|
this->super.destruct((Object_t*) this);
|
||
|
}
|
||
|
|
||
|
// Now the interesting part: The populate method.
|
||
|
void method(TestClass, populate)(TestClass_t* obj, class_t c) {
|
||
|
// This method basically does the setup of the object.
|
||
|
|
||
|
// First of all we have to populate the super object.
|
||
|
// (In this case of type Object_t)
|
||
|
populate(Object)((Object_t*) obj, c);
|
||
|
|
||
|
// Let's set up all fields and methods.
|
||
|
|
||
|
obj->string = NULL;
|
||
|
|
||
|
add_method(obj, TestClass, destruct);
|
||
|
add_method(obj, TestClass, clone);
|
||
|
add_method(obj, TestClass, set);
|
||
|
add_method(obj, TestClass, get);
|
||
|
|
||
|
// Done.
|
||
|
}
|
||
|
```
|
||
|
|
||
|
TODO: the remaining 90 % of the readme (Don't judge me, it's half past 1 am and I have to get up early tomorrow.)
|