Короткая история препроцессора «Утюг»
Анонимный читатель хорошо знает про мою любовь к препроцессорам. Я однажды уже делал один, он назывался Агидель. Хорошая вещь была! Но в итоге я разочаровался в идее и написал про это статью.
Язык си не даёт мне покоя. Этот язык прямо просит, чтобы к нему написали классный препроцессор. Я понял ошибку подхода в Агидели и придумал ещё один препроцессор. Назвал его «Утюг».
Моё имя, Тимур, обозначает железо. По-английски железо — iron. Iron также обозначает утюг.
Что он умеет?
Мне нравятся директивы препроцессора си:
#include "file.h"
#define DEBUG
void log(char *msg) {
#ifdef DEBUG
printf("%s", msg);
#endif
return;
}
Мне нравятся слова-перевёртыши в баше и где-нибудь ещё:
case $var in
1)
echo 1
;;
*)
echo not 1
;;
esac
# case и esac!
Фишка утюга в том, что его блоки обёрнуты в си-препроцессорно-подобные директивы, при этом открывающая и закрывающая обратны. Ну короче, потом понятно будет.
import
Убирает кучи #include
ов, которые есть в любом проекте на си или его прямых
потомках. Писать надо меньше, даже эти угловые скобки необязательны! Кайф.
#import
stdio.h
stdlib.h
<string.h>
"myfile.h"
#tropmi
// превращается в
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "myfile.h"
doc
Обычный комментарий, но с дополнительной семантической загрузкой. В ней должна быть документация. Эта штука сломала мне подсветку синтаксиса в емаксе, кстати.
#doc
Does str start with such prefix?
#cod
int prefix(const char* pre, const char* str) {
return strncmp(pre, str, strlen(pre)) == 0;
}
// превращается в
/*
* Does str start with such prefix?
*/
int prefix(const char* pre, const char* str) {
return strncmp(pre, str, strlen(pre)) == 0;
}
def
Создаёт функции. Можно выбрать желаемый стиль, в этом главная фишка.
// постфиксный стиль
#def sum (a, b: int): int
return a + b;
#fed
// традиционный стиль
#def int sum (int a, int b)
return a + b;
#fed
// превращается в
int sum (int a, int b) {
return a + b;
}
monoid
Я прочитал умную статью про моноиды, где очень академично рассказывали, что это такое. Я так понял, что это функции, которые получают два аргумента одного типа и возвращают результат того же типа, а ещё у них есть стандартное значение. Я решил, что юзать такое в си — верх крутости, но для этого нужен хороший и красивый синтаксис. Вот такой:
#monoid sum int 0
$1 + $2
#dionom
// Превращается в моноид как-нибудь. Я вот так сходу не напишу функцию с
// вариативным количеством аргументов.
Имплементация
Вдохновлён, сел кодить. Конечно же, на си. Узнал много нового, вроде разобрался с указателями. Оказывается, можно сделать указатель на функцию!
Я реализовал только #import
и #doc
. Резко понял, что подобный препроцессор
не так уж и нужен. Ну, я человек простой, сразу пошёл писать про это в свой
блог.
Вот на этих двух директивах и остановлюсь, наверное.
Исходный код решил не загружать на гитхаб, потому что не хочу оформлять репозиторий для одного файла. Скачать этот файл можно тут (4 КБ).
Использование
Переходим в папку, куда скачали файл iron.c
. Компилируем:
gcc iron.c -o iron
Запускаем:
./iron input-file > output-file
Всё.