99 бутылок: препроцессор Си
Те программисты, кто каким-либо образом сталкивался с языком Си, знают, что у него есть так называемый препроцессор. Его директивы начинаются с символа решётки и выполняются до компиляции программы. Обычно его используют, чтобы включить одни файлы в другие, определить макрос или проверить значение каких-либо внутренних флагов компилятора.
С тех пор как мне много лет назад кто-то показал как можно на нём писать программы, меня не оставляла идея написать на нём «песню о пиве».
80. Препроцессор Си, разумеется, не задумывался как что-то на чём будут писать программы, поэтому, чтобы добиться цели, приходится использовать россыпь хитростей и разнообразных хаков. Не думаю, что имеет смысл их описывать, тем более не я их придумал. В интернете есть достаточно подробных статей по этой теме, как на английском, так и на русском.
Изучение этих материалов было не только для меня интересным делом, но и полезным — я лучше понял как работает препроцессор в Си, а так же узнал кое-что новое, являющееся, правда, расширением языка.
Вопреки обыкновению, вывод программы я обрабатываю другой утилитой (см. строку запуска). Это приходится делать, так как в этом языке нет простого способа вывести перевод строки. Можно было бы сделать это ANSI-кодами, но их не будет видно в листинге кода, что меня не устраивает.
#if 0
Bolknote.ru, 2024.11.05
To run:
gcc -P -E - < 99 | sed 's/\\n */\n/g'
#endif
#define CAT(a, b) a##b
#define SECOND(a, b, ...) b
#define TEST(...) SECOND(__VA_ARGS__, 0)
#define LF \n
#define DEC(n) CAT(DEC_,n)
#define DEC_0 9
#define DEC_1 0
#define DEC_2 1
#define DEC_3 2
#define DEC_4 3
#define DEC_5 4
#define DEC_6 5
#define DEC_7 6
#define DEC_8 7
#define DEC_9 8
#define DEC_X0(a, b) IF_ELSE(ISZERO(b)) (DEC(a)) (a)
#define ISEND(a, b) TEST(ISEND_ ## a ## b)
#define ISEND_01 ,1
#define ISZERO(n) TEST(ISZERO_ ## n)
#define ISZERO_0 ,1
#define ISONE(n) TEST(ISONE_ ## n)
#define ISONE_1 ,1
#define IF_ELSE(b) CAT(IF_, b)
#define IF_0(i) ELSE_0
#define IF_1(i) i ELSE_1
#define ELSE_0(e) e
#define ELSE_1(e)
#define PLUR(n) IF_ELSE(ISONE(n)) (bottle) (bottles)
#define PRINT_PLUR(a, b) IF_ELSE(ISZERO(a)) (b PLUR(b)) (CAT(a, b) bottles)
#define PRINT_B(a, b) IF_ELSE(ISZERO(b)) \
(IF_ELSE(ISZERO(a)) \
(No bottles) \
(PRINT_PLUR(a, b)) \
) \
(PRINT_PLUR(a, b)) of beer
#define PRINT_ROW(a, b) \
PRINT_B(a, b) on the wall, PRINT_B(a, b)! LF \
Take on down, pass it around, LF \
PRINT_B(DEC_X0(a, b), DEC(b))! LF LF
#define STOP(...) No more bottles of beer on the wall, LF \
No more bottles of beer! LF \
Go to the store and buy some more, LF \
PRINT_B(9, 9) on the wall!
#define EMPTY()
#define DEFER(...) __VA_ARGS__ EMPTY()
#define CROSS1(a, b) PRINT_ROW(a, b) \
DEFER(IF_ELSE(ISEND(a, b)) (STOP) (CROSS2)) (DEC_X0(a, b), DEC(b))
#define CROSS2(a, b) PRINT_ROW(a, b) DEFER(CROSS1) (DEC_X0(a, b), DEC(b))
#define MUL0(...) MUL1(MUL1(MUL1(MUL1(__VA_ARGS__))))
#define MUL1(...) MUL2(MUL2(MUL2(MUL2(__VA_ARGS__))))
#define MUL2(...) MUL3(MUL3(MUL3(MUL3(MUL3(__VA_ARGS__)))))
#define MUL3(...) __VA_ARGS__
#define RUN(a, b) MUL0(CROSS1(a, b))
RUN(9, 9)