Про́клятый огонь, или магия препроцессора C
18 мин Ненормальное программирование *Программирование *Разработка игр *Компиляторы *C *
Задавались ли вы когда-нибудь вопросом, можно ли полноценно программировать при помощи директивы #define в языке C? Полнота по Тьюрингу шаблонов C++ известна весьма широко, например, люди пишут трассировщики лучей, делающие все вычисления во время компиляции (вместо времени исполнения). А как обстоят дела с препроцессором C? Вопрос оказался сильно нетривиальнее, и эта история является, на мой вкус, отличным анекдотом для курса лекций по теории компиляторов, что я готовлю в данный момент. В частности, для лучшего понимания происходящего здесь, рекомендую ознакомиться со второй статьёй, которую я опубликовал параллельно этой: лексер и парсер.
Чтобы не было обманутых впечатлений, предупрежу сразу, что рейтрейсера не будет, но про́клятый код будет очень даже! Итак, поехали. Для начала, почему я вообще задался этим вопросом? Если обычный код компьютерной графики вам скучен, следующий раздел можно пропустить, перематывайте до последней картинки.
Содержание статьи:
Самый обычный огонь, никем не про́клятый
Как я и сказал, я пообещал написать простейший, но вполне полноценный компилятор только что придуманного мной языка wend за выходные. Написать-то дело несложное, а вот описать труднее. Для хорошего описания нужны красочные примеры. У меня аллергия на иллюстрации из разряда вычислений чисел Фибоначчи. Ну сколько же можно?! Поскольку wend крайне примитивен, то и примеры мне нужны простые, но всё же как можно более эффектные. И тут я вспомнил про древнюю демосцену! Вот, например, я хочу написать на своём языке программу, которая просто гоняет по кругу пламя:
Это сделать несложно: у меня нет возможности запускать графический режим, но ведь моя консоль поддерживает управляющую последовательность 33[, так что для отрисовки пламени мне вполне хватит одной инструкции print! Кстати, я слышал, что нынче даже в винде консоль поддерживает эскейп-последовательности ANSI, но лично не проверял.
Дело за малым, написать код. Поскольку я болен только на часть головы, а не на всю, писать я его буду сначала на C, и уж потом только транслировать (руками) в wend, поскольку мой компилятор — это хорошо, но всё же вокруг си инструментов самую малость больше. Да и баги в моём компиляторе никто не отменял, и мне лень думать, где именно у меня проблема. На баги gcc я уже, конечно, натыкался, но это исчезающе редкое явление.
Давайте посмотрим, как создать такой огонь, а потом продолжим разговор про препроцессор и чёрную магию. Вот так выглядит обёртка, от которой мы будем отталкиваться:
Hidden text#include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <unistd.h> #define WIDTH 80 #define HEIGHT 25 #define FPS 30 const char* palette[256] = { #define ANSIRGB(R,G,B) «