В данной статье рассмотрим препроцессорные парсеры. Кроме того, в статье приведены большое количество примеров с пояснениями, каждая строка которых полностью аргументирована. Статья рассчитана на уже более-менее опытных программистов.
Введение
Препроцессорные парсеры – это конструкции из препроцессорных директив define, которые необходимы для определения отрывка кода по заданному шаблону. Подобные конструкции можно рассматривать, как более продвинутые манипуляции с компилятором.
Именно данные парсеры послужили основой для множества лучших библиотек. Например, всеми любимая библиотека foreach, которая образует новый псевдо-оператор foreach со своим синтаксисом и т.д.
Про алгоритмы, использованные в библиотеках, можно говорить очень долго, но суть всего этого одна – они анализируют код, отсеивают ненужные части, а потом переопределяют уже существующий код на этапе пре-компиляции. Это чем-то похоже на метапрограммирование.
Рассмотрим пример:
PHP код:
#define SOME_PARSE: _:SOME_MACRO_TAG_0:SOME_MACRO_TAG_1:
#define SOME_MACRO_TAG_0:SOME_MACRO_TAG_1:%1<%2>%0[%3] %1,%2,%3
#define SOME_MACRO_TAG_1:%1[%3] %1,cellmax,%3
main()
{
printf("%d %d %d %d", SOME_PARSE:5<8>[10]);
printf("%d %d %d %d", SOME_PARSE:5[10]);
}
Теперь рассмотрим каждую строчку более подробнее. Начнём.
- Первая строчка необходима для упрощения вызова данного парсера.
- Вторая строчка необходима для определения шаблона %1<%2>%0[%3] в строке. Заметим, что между символом ">" и "[" стоит нуль. Данный нуль необходим для определения пробелов между данными символами, т.е. для написания подобным образом:
PHP код:
SOME_PARSE:5 <8> [10]
- Третья - альтернатива. То есть, если вторая строка не подходит по шаблону то значит, что альтернативная для этого только третья.
При использовании флага -l при компиляции, создаётся файл с расширение ".lst", который содержит результат работы препроцессора. Давайте рассмотрим результат выше приведенного кода:
PHP код:
printf("%d %d %d %d", _:5,8,10);
printf("%d %d %d %d", _:SOME_MACRO_TAG_0:5,cellmax,10);
Заметим то, что остался тег SOME_MACRO_TAG_0, но мы его обезвредили "нулевым" тегом, иначе была бы ошибка из-за несовпадения тегов. Таким образом, вы можем вместо символа "_" подставлять совершенно любые теги, однако главное - чтобы функция поддерживала данный тег.
Hash
Теперь давайте рассмотрим алгоритм хеширования, построенный полностью на этапе пре-компиляции. Однако большой минус такой реализации - разные регистры.
PHP код:
#define hash_alg(%0) h@(%0,END,END)(0)
#define h@(%0,%1)(%8) h@%0(%1)(%8)
#define h@END(%1)(%8) %8
#define h@a(%1)(%8) h@(%1)(%8 + 158)
#define h@b(%1)(%8) h@(%1)(%8 + 682)
#define h@c(%1)(%8) h@(%1)(%8 + 333)
Итак, давайте рассмотрим каждую строчку по отдельности:
- Первая строка данного кода определяет начальную точку.
- Вторая строка содержит макрос, отделяющий один аргумент от остальных.
- Четвёртая строчка необходима для вывода результата, т.к. это последняя точка этого алгоритма.
- Пятая строка содержит алгоритм вычисления только для символа a. В последующих же строках, алгоритм в вторых скобках может изменяться.
Код:
#define h@b(%1)(%8) h@(%1)(%8 + 682)
Примечание: все цифры - 158, 682, 333 - придуманы, вместо них может стоять какой-либо алгоритм, либо цифра.
Теперь необходимо вызвать макро-функцию, например, в printf для вывода результата:
PHP код:
main()
{
printf("%d", hash_alg(a,b,c,c,b,a));
}
Результатом будет число 2346, т.к. 0 + 158 + 682 + 333 + 333 + 682 + 158 = 2346
Сложные алгоритмы
Отличие минимальной сложности от максимальной является более уникальный и сложный алгоритм, которые конструируется только для определённой библиотеки. Такие алгоритмы немного напоминают
обфускацию, что сильно уменьшает читаемость кода.
Пример:
PHP код:
// Точка входа. <> необходим при последующего анализа кода.
#define dialog%1(%2) DPARSE(%1<>%2)
// Извлекаем содержимое из <>, если оно там присутствует.
#define DPARSE(%1<%3>%2) forward pdR_%1(playerid,response,listitem,inputtext[]);public pdR_%1(playerid,response,listitem,inputtext[]){return g@R(%3,%1,response)(%2)
// Тоже самое, что и первый пример в этой статье, т.е. это отсеивание. Разделитель "$", в других библиотеках может быть "|||" или "||||" и т.п.
#define g@R(%3,%1,%4)(%2) _:D@A0:D@A1:%1$%2$%3$%4$
// Если содержимое в <> отсутствует, значит переопределяем по данному алгоритму:
#define D@A0:D@A1:%1$%2$$%4$ _dR_%1(D@AN:%2[]$); } stock _dR_%1(%2)
// Иначе, если %2 содержит <>, то в пользовательском коде <...> присутствует. Кроме того, это сделано для отделения <> от аргументов.
#define D@A1:%1$<>%2$%3$%4$ (_:(%3)==%4) ? _dR_%1(D@AN:%2[]$) : 0; } stock _dR_%1(%2)
// Данные строки нужны для избавления от пробела. Если бы этого не было, то мы бы получили ошибку.
#define pdR_%0\32;%1(%2) pdR_%0%1(%2)
#define _dR_%0\32;%1(%2) _dR_%0%1(%2)
// Данная строка уничтожает [] в строке для избежания ошибок, т.к. при вызове функции ненужно указывать квадратные скобки.
#define D@AN:%2[%3]%0$) %2)
Данный код переопределяет псевдо-оператор dialog в компилируемый код. Давайте рассмотрим результат выполнения данного кода:
PHP код:
forward pdR_PlayerReg(playerid,response,listitem,inputtext[]);
public pdR_PlayerReg(playerid,response,listitem,inputtext[])
{
return _:D@A0:(_:(true)==response)?_dR_PlayerReg(playerid, inputtext):0;
}
stock _dR_PlayerReg(playerid, inputtext[])
{
printf("Your text: %s", inputtext);
}
Даже, не компилируя, можно определить работоспособность кода.
Итог
Препроцессорные парсеры позволяют сделать код более гибким, менее затратным на написание, а также более читаемым. Таким образом, можно создать собственный синтаксис для библиотек.
Примеры лучших работ
Если у вас появились какие-то вопросы, или вы думаете, что я что-то забыл, напишите об этом.
Автор: VVWVV
Исключительно для pro-pawn.ru
Копирование данной статьи на других ресурсах без разрешения автора запрещено!