Как я писал Foundation для С

в 11:47, , рубрики: c++, define, библиотеки с, ненормальное программирование, ооп

image Долгое время программирования на 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js