Короткая история препроцессора «Утюг»

Анонимный читатель хорошо знает про мою любовь к препроцессорам. Я однажды уже делал один, он назывался Агидель. Хорошая вещь была! Но в итоге я разочаровался в идее и написал про это статью.

Язык си не даёт мне покоя. Этот язык прямо просит, чтобы к нему написали классный препроцессор. Я понял ошибку подхода в Агидели и придумал ещё один препроцессор. Назвал его «Утюг».

Моё имя, Тимур, обозначает железо. По-английски железо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

Всё.