Это сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

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)