Не так давно я презентовал здесь свой велосипед - c-oop-gen: ООП в Си. А именно генератор сишного кода в ООП стиле из XML описания. Тогда мне объяснили, что XML не лучшая идея для представления программного кода. Так что встречайте мой новый велосипед - https://github.com/KivApple/c_ext.
Это что-то вроде препроцессора для Си, который добавляет в язык несколько новых конструкций.
Все эти конструкции связаны с определением структур. Во-первых, структуры теперь можно наследовать.
struct A {
int a;
};
struct B: A {
int b;
};
При этом очень немаловажен тот факт, что указатель на B совместим с указателем на A (но не наоборот). Под капотом там незаметно вставляется приведение типов, если оно необходимо.
Во-вторых, у структуры теперь могут быть методы:
struct A {
int f();
};
int A::f() {
return 100500;
}
Инлайнить (реализовывать методы прямо внутри структуры) нельзя, но я считаю, что это не очень нужно, ибо это таки С, а не С++ и программист должен явно определять, в каком исполняемом модуле окажется функция (а если очень хочется, может описать её как static в момент реализации и поместить в заголовочный файл).
Всем нестатическим методам автоматически передаётся указатель на структуру this, с которым можно свободно работать.
В-третьих, у структур теперь могут быть статические члены:
struct A {
static int i;
static void test();
};
int A::i;
void A::test() {
...
}
В-четвёртых, у структуры могут быть виртуальные методы. Правда, чтобы они работали, нужно обязательно объявить конструктор - нестатичный метод с именем construct, а также вызвать его до любого вызова виртуального метода.
В-пятых, можно явно обратиться к родительской реализации. Как-то так:
struct A {
void f();
};
struct B: A {
void f();
};
void B::f() {
this->A::f();
}
Данный транслятор должен понимать большинство gcc-змов. Во всяком случае я проверил его на нескольких стандартных заголовочниках (а они полны gcc-змов) и он всё скушал, а на выходе получился валидный код. Однако крайне не рекомендуется применять всякие атрибуты к структурам и их членам, если вы используете новые возможности (если нет, то определения не будут изменены, а вот если транслятор в них уж полез, то он может съесть что-то неподдерживаемое).
Также транслятор сам по себе понимает директивы #line и генерирует свои после окончания обработки. То есть ему можно подавать на вход результат работы любого препроцессора (cpp, m4 и т. п.), а в сообщениях об ошибках компилятора будут адекватные указания, где произошла ошибка.
Несмотря на то, что транслятор имеет достаточно информации для того, чтобы отлавливать многие ошибки (например, неопределённые идентификаторы или несовместимость типов), он старается этого не делать и падать только на тех ошибках, которые специфичны именно для него (например, попытка унаследоваться не от структуры).
Логика простая: компиляторы обычно выдают очень хорошие сообщения об ошибках, а мои сообщения могут оказаться недостаточно информативными. К тому же мой транслятор выходит после первой же ошибки, а большинство компиляторов выводят все найденные. А ещё я могу воспринять за ошибку какой-то расширение компилятора. Так что я просто стараюсь оставить насколько это возможным неизменными все непонятные места.
В общем, вот такие дела. Жду ваших комментариев.
Особенно меня интересует вопрос: что делать с конструкторами, собственно? Можно вызывать их в C++ стиле - то есть в момент объявления переменной (или выделения памяти). Это хорошо, конечно, что никто не забудет вызвать конструкторы, но как-то недостаточно Plain C-way. А что если мы хотим выделить память, а конструктор вызывать лишь спустя какое-то время? От ответа на этот вопрос зависит и судьба деструкторов - если конструктор гарантированно вызывается, то мне не проблема автоматически вызвать деструктор. Но в текущем виде автоматическому вызову деструктора не место (а вдруг объект не был сконструирован?).
Также интересует нужность перегрузки функций. Я могу сделать манглинг имён (а чтобы сохранить совместимость с Си, не менять имя первого объявления функции), но насколько он нужен?
P. S.: Планы на будущее: поддержка лямбд и асинхронного программирования.
UPD1: Добавил поддержку анонимных функций, в том числе с захватом переменных из текущего контекста по ссылке или по значению. См. README.
UPD2: Добавил начальную поддержку самого главного, ради чего всё затевалось. А именно - поддержку асинхронного программирования. Теперь можно вызвать функцию, возвращающую void и ожидающую делегата в качестве последнего аргумента (у меня делегат - это любая структура, у которой первое поле это указатель на функцию, которая первым аргументом принимает указатель на эту структуру), не указывая значение для этого самого аргумента. В результате случится магия и текущая функция станет асинхронной (функция, которая подтвергается данной метаморфозе, должна возвращать void и не принимать переменное число аргументов). Переменные, которые используются одновременно по разные стороны от асинхронного вызова будут сохранятся в специальную структуру состояния. Сама структура состояния будет создана с помощью malloc при входе в асинхронную функцию и освобождена при выходе (будь то естественное завершение или выход с помощью return). Пока в реализации есть косяки, но я их исправлю. Внутри асинхронной функции категорически не рекомендуется использовать goto, ибо это сломает алгоритм определения какие переменные нужно сохранять при асинхронном вызове.