Долгое время программирования на Obj-C оставляет свой отпечаток на челе программиста. Кто видел этот красивый Foundation, написанный еще во времена NeXTSTEP, т.е. приблизительно в 1987-89 годы (кто знает более точную дату, поправте меня). Вся красота чистого ООП-программирования, доступная в Obj-C слилась в NS, который действительно был next step'ом по качеству и красоте кода. Это было создание, почти всех необходимых базовых средств разработки:
— строки и локализация;
— контейнеры (динамический массив, словарь (он же NSDictionary, для С++-ов привычнее слово map), даты (NSDate));
— потоки;
— графика;
— архиватор обьектов и другие плюшки.
Наследование от единого базового класса, четкое разделение интерфейса, протокола, помогло создать очень гибкий ООП-фреймворк, который является очень удобным в использовании (те кто использовали, меня поймут).
На С++ существует штука, подобная NS Foundation, которая называется Boost, там есть, почти все, что можно придумать – от контейнеров, алгоритмов, графов до работы с сетью и т.д.
Но вот проблема, на С такой библиотеки нету (а, может я просто плохо искал). Хоть, и будет сказано, что программировать прикладные программы на С тяжко, и С скорее для системного программирования, я скажу, что С очень гибкий, и при правильном применении можно сделать так, чтобы на С также удобно стало программировать и прикладные программы (на нем можно даже написать ООП и Java).
Решение написать RayFoundation, в котором будут находится базовые инструменты, пришло скорее как желание по-исследовать возможности С, и улучшить скилл написания программ на нем.
На данный момент сыро(но в рабочем состоянии) реализованы таки пункты:
- базовый динамический массив (RArray)
- базовые нуль-терменированные строки (RCString)
- базовый словарь (RDictionary)
- некоторые операции с байтовыми массивами (RByteOperations)
- таблица идентификации (эта идея ко мне пришла, из-за необходимости хоть какого-нибудь учета обьектов) (RClassTable)
- словарь строк (RStringDictionary)
Я собирался и собираюсь использовать все это для написания мини-виртуальной машини, тоже в качестве исследования, но буду рад, если это пригодится любому С, (С++, Obj-C) кодеру. Все это дело написано на адском define-based синтаксисе, который делает понимание кода легче? Ну для меня, по крайней мере, он местами выглядит красивее, чем нативный С. И еще, я использую Верблюжий Регистр.
Чтобы идти далее, нужно увидеть некоторые из них (полностью доступен по ссылке ):
// declarations
#define class(className) typedef struct className {
uint64_t classId;
#define protocol(protocolName) typedef struct protocolaName {
#define discipleOf(className) className *splitUp(master, splitUp(className, Object));
#define endOf(className) } className;
splitUp – это такая штука, которая смотрит, что внутри define-ов и склеивает их
#define _splitUp(one, two) one##two
#define splitUp(one, two) _splitUp(one, two) // splits up two words: splitUp(Hello,World) -> HelloWorld
Таким образом можно понять, что ключевые слова class(), endOf(), protocol() объявляют структуры, с некоторым именем, при этом отличие у class() и protocol(), в том, что в класс неявно добавляется uint64_t classId – переменная, это в будущем будет идентификатор класса, к которому относится структура(далее я буду использовать слово объект).
Слово discipleOf() обьявляет в классе указатель на переменную типа masterClass с именем masterClassNameObject, например: discipleOf(Hello) превратится в Hello *masterHelloObject.
Далее:
#define method(returnValue, methodName, className) returnValue splitUp(methodName, className)(className *object
#define constructor(className) className* splitUp(constructorOf,className) (className *object
#define destructor(className) void splitUp(destructorOf,className) (className *object)
#define printer(className) void splitUp(printerOf,className) (className *object)
#define singleton(className) className* splitUp(singletonOf,className)(void)
Тут сборная солянка для классических конструктора-деструктора-синглетона-принтера, т.е. это функции, которые выполняются согласно своиму имени. Т.е. конструктор создает объект, деструктор его разрушает, принтер печатает, а синглетон создает доступ к объекту класса, который будет один (sharedInstance). И во всех их передается неявный объект типа className.
Когда я все это написал, я столкнулся с проблемой, что нужно отличать declarations(определения) и calls(вызовы) методов. Об этом далее.
Еще немного кода define'ов:
#define method(returnValue, methodName, className) returnValue splitUp(methodName, className)(className *object
Ключевое слово method объявляет функцию типа returnValue nameClassName(ClassName *object, и далее определение параметров программистом, как в классическом С. Тут важный момент, что везде неявно передается первым параметром указатель на объект(object), это будетаналог this и self.
Для создания нотации типа object.method() или [object method], я сделал маленький хак, из неиспользуемого символа $:
#define $(object, methodName) methodName(object
Этот хак позволяет писать так: $(object, methodName), args);
Вот как раз место, где нужно разделять вызовы метода и его определение, для вызовов созданы маленькие буковки:
#define c(className) splitUp(constructorOf, className) // constructor function name
#define d(className) splitUp(destructorOf, className) // destructor function name
#define p(className) splitUp(printerOf, className) // printer function name
#define m(methodName, className) splitUp(methodName,className) // some method function name
#define master(object, masterClassName) object->splitUp(master,splitUp(masterClassName, Object)) // call to masterClassObject
#define singletonCall(className) splitUp(singletonOf,className)() // singleton call
Таким образом можно писать вот так: $(object, m(methodName, ClassName)), args );
или $(object, p(ClassName)) );
Также миниплюхи для циклов:
#define forAll(iterator, count) for(iterator = 0; iterator < count; ++iterator)
#define fromStartForAll(iterator, start, count) for(iterator = start; iterator < start + count; ++iterator)
Таким синтаксисом и будет полон следующий код.
Разберем создание типичного обьекта, пусть это будет RArray (полный код):
class(RArray) //--------------------------------------------------------------
members
uint64_t startSize; // start size of array in elements
uint64_t sizeMultiplier; // size multiplier when auto-add-size
uint64_t count; // count of elements in array
uint64_t freePlaces; // count of free places for elements
void (*destructorDelegate)(pointer); // destructor of elements delegate
void (*printerDelegate)(pointer); // printer of elements delegate
pointer *array; // array
endOf(RArray) //--------------------------------------------------------------
// constructor - destructor - printer
constructor (RArray), RArrayFlags *error);
destructor (RArray);
printer (RArray);
При препроцессинге превращается в это вот:
typedef struct RArray { uint64_t classId;
uint64_t startSize;
double sizeMultiplier;
uint64_t count;
uint64_t freePlaces;
void (*destructorDelegate)(pointer);
void (*printerDelegate)(pointer);
pointer *array;
} RArray;
RArray* constructorOfRArray (RArray *object, RArrayFlags *error);
void destructorOfRArray (RArray *object);
void printerOfRArray (RArray *object);
Немного методов:
// allocation - reallocation
method(RArrayFlags, addSize, RArray), uint64_t newSize);
method(void, flush, RArray));
method(byte, sizeToFit, RArray));
будут:
RArrayFlags addSizeRArray(RArray *object, uint64_t newSize); // такого вот типа.
В имплементации ненадо ничего менять, просто мор скобочек:
method(RArrayFlags, addSize, RArray), uint64_t newSize) {
#if RAY_SHORT_DEBUG == 1
RPrintf("RA %p ADD_SIZEn", object);
#endif
if(newSize > object->count) {
static uint64_t iterator;
// create temp array
pointer *tempArray = RAlloc((size_t) (newSize * sizeof(pointer)));
if (tempArray == NULL) {
return temp_allocation_error;
} else {
// copy pointers to temp array
forAll(iterator, object->count) {
tempArray[iterator] = object->array[iterator];
}
deallocator(object->array); // delete old
object->array = tempArray; // switch to new
object->freePlaces = newSize - object->count; // add some free
return no_error;
}
} else {
RPrintf("Warning. RA. Bad new size, do nothing, please delete function call, or fix it.n");
return bad_size;
}
}
Кстати, RAlloc это #define RAlloc malloc,
RPrintf — #define RPrintf printf
Вот так вот вот выглядит мой define-based код.
А теперь немножко примеров с комментариями:
#include "RayFoundation/RArray/RArray.h"
void printString(char *src) {
printf("%s n", src);
}
byte stringSorter(char *first, char *second) {
if (first != second) {
if (first[6] > second[6]) {
return swap_objects;
}
}
return 0;
}
int main(int argc, const char *argv[]) {
// cоздаем динамический массив
RArray *dynamicArray = makeRArray();
dynamicArray->printerDelegate = printString;
dynamicArray->destructorDelegate = free;
for (int i = 0; i < 10; ++i) {
char *a = malloc(sizeof(char) * 8);
memmove(a, "Hello ", sizeof("Hello "));
a[6] = (char) (i % 10 + 48);
addObjectToRA(dynamicArray, a);
}
// печатаем
printRA(dynamicArray);
// встроенная сортировка
sortRA(dynamicArray);
printRA(dynamicArray);
// сортируем с делегатом
$(dynamicArray, m(quickSortWithDelegate, RArray)), 0, dynamicArray->count - 1, stringSorter);
printRA(dynamicArray);
// получаем подмассив, будет 2 ошибки в консоли,
// а в подмассиве два null-елемента
RArray *sub = $(dynamicArray, m(getSubarray, RArray)), makeRRange(1, 11));
printRA(sub);
// быстро удаляем обьекты
$(dynamicArray, m(fastDeleteObjectAtIndexIn, RArray)), 9);
printRA(dynamicArray);
// подгоняем размер (нет пустых мест)
sizeToFitRA(dynamicArray);
printRA(dynamicArray);
// удаляем массив
deleteRA(dynamicArray);
deallocator(sub);
//--------------------------------------------------
// создаем c-string массив
RArray *stringArray = makeRArray();
// устанавливаем делегаты как у строки
stringArray->printerDelegate = p(RCString);
stringArray->destructorDelegate = d(RCString);
for(unsigned i = 0; i < 10; ++i) {
// генерируем простые строки с помощью rand()
addObjectToRA(stringArray, randomRCString());
}
printRA(stringArray);
// удаляем подмассив с 5, длинной 4
$(stringArray, m(deleteObjects, RArray)), makeRRange(5, 4));
printRA(stringArray);
}
github.com/kojiba/RayLanguage тут можно найти еще больше примеров, кому понравилось, посмотрите еще словарь и classTable.
Спасибо за внимание, надеюсь, кому-нибудь пригодится.
Автор: StrangerInRed