Стиль C: макросы или препроцессор?

Я написал библиотеку для сопоставления строк с набором шаблонов, и теперь я могу легко встраивать лексические сканеры в программы на C.

Я знаю, что существует много хорошо зарекомендовавших себя инструментов для создания лексических сканеров (lex и re2c, если назвать только первые два, которые приходят на ум) этот вопрос не о лексерах, а о наилучшем подходе к "расширению" синтаксиса C . Пример с лексером — это просто конкретный случай общей проблемы.

Я вижу два возможных решения:

  1. напишите препроцессор, который преобразует исходный файл со встроенным лексером в простой файл C и, возможно, в набор других файлов, которые будут использоваться при компиляции.
  2. напишите набор макросов C, чтобы представить лексеры в более удобочитаемой форме.

Я уже сделал и то, и другое, но возникает вопрос: "какой из них вы считаете лучшей практикой в ​​соответствии со следующими критериями?"

  • Читаемость. Логика лексера должна быть ясной и легкой для понимания
  • Ремонтопригодность. Поиск и исправление ошибки не должно быть кошмаром!
  • Вмешательство в процесс сборки. Препроцессору потребуется дополнительный шаг в процессе сборки, препроцессор должен быть в пути и т.д.

Другими словами, если бы вам пришлось поддерживать или писать программу, использующую один из двух подходов, какой из них разочарует вас меньше?

В качестве примера, вот лексер для следующей задачи:

  • Суммируйте все числа (может быть в десятичной форме, включая экспоненциальную, например 1.3E-4.2)
  • Пропускать строки (двойные и одинарные кавычки)
  • списки пропуска (аналогично спискам LISP: (3 4 (0 1)() 3))
  • останавливаться при встрече с концом слова (регистр не имеет значения) или в конце буфера

В двух стилях.

/**** SCANNER STYLE 1 (preprocessor) ****/
#include "pmx.h"

t = buffer

while (*t) {
  switch pmx(t) { /* the preprocessor will handle this */
    case "&q" :         /* skip strings */
      break; 

    case "&f<?=eE>&F" : /* sum numbers */ 
      sum += atof(pmx(Start,0));
      break;

    case "&b()":        /* skip lists */
      break;

    case "&iend" :      /* stop processing */ 
      t = "";
      break;

    case "<.>":         /* skip a char and proceed */
      break;
  }
}

/**** SCANNER STYLE 2 (macros) ****/
#include "pmx.h"
/* There can be up to 128 tokens per scanner with id x80 to xFF */
#define TOK_STRING x81
#define TOK_NUMBER x82
#define TOK_LIST   x83
#define TOK_END    x84
#define TOK_CHAR   x85

pmxScanner(   /* pmxScanner() is a pretty complex macro */
   buffer
 ,
   pmxTokSet("&q"         , TOK_STRING)
   pmxTokSet("&f<?=eE>&F" , TOK_NUMBER)
   pmxTokSet("&b()"       , TOK_LIST)
   pmxTokSet("&iend"      , TOK_END)
   pmxTokSet("<.>"        , TOK_CHAR)
 ,
   pmxTokCase(TOK_STRING) :   /* skip strings */
     continue; 

   pmxTokCase(TOK_NUMBER) :   /* sum numbers */ 
     sum += atof(pmxTokStart(0));
     continue;

   pmxTokCase(TOK_LIST):      /* skip lists */
     continue;

   pmxTokCase(TOK_END) :      /* stop processing */ 
     break; 

   pmxTokCase(TOK_CHAR) :     /* skip a char and proceed */
     continue;
);

Если кого-то заинтересует текущая реализация, код находится здесь: http://sites.google.com/site/clibutl .


person Remo.D    schedule 28.03.2009    source источник


Ответы (2)


Препроцессор предложит более надежное и универсальное решение. Макросы, с другой стороны, быстро разрабатываются, обеспечивают хорошее доказательство концепции и просты, когда пространство для ключевого слова / токена невелико. Масштабирование/включение новых функций может стать утомительным с макросами через некоторое время. Я бы сказал, для начала создайте макросы, а затем преобразуйте их в команды препроцессора.

Кроме того, постарайтесь использовать общий препроцессор, а не писать свой собственный, если это возможно.

[...] У меня были бы другие зависимости для обработки (например, m4 для Windows).

да. Но так будет и с любым решением, которое вы напишете :) -- и вам придется его поддерживать. Для большинства программ, которые вы называете, доступен порт Windows (например, см. m4 для Windows). Преимущество использования такого решения заключается в том, что вы экономите много времени. Конечно, недостатком является то, что вам, вероятно, придется быстро работать с исходным кодом, если и когда обнаружится странная ошибка (хотя люди, поддерживающие их, очень полезны и, безусловно, позаботятся о том, чтобы у вас была любая помощь).

И опять же, да, я бы предпочел пакетное решение, чем собственное.

person dirkgently    schedule 28.03.2009
comment
Конечно, я мог бы использовать общий препроцессор (например, gema, m4 или даже awk или perl) для создания конкретного препроцессора, но у меня были бы другие зависимости для обработки (например, m4 для Windows). Не лучше ли было бы напрямую предоставить препроцессор как часть самого пакета? - person Remo.D; 28.03.2009
comment
Кстати, я получаю ваш ответ как +1 за решение препроцессора :) - person Remo.D; 28.03.2009
comment
На самом деле не обязательно, чтобы выбранный генератор/препроцессор кода был доступен на всех целевых платформах. Это должно быть только на платформах, которые вы хотите использовать для разработки. Поскольку сгенерированные файлы C будут переносимыми (я так думаю), вы можете упаковать и отправить их вместе с исходным кодом. Например, для проектов, использующих Flex или Bison, обычной практикой является распространение сгенерированных файлов со своими архивами, поэтому пользователям, которые хотят просто скомпилировать пакет, не нужно устанавливать эти инструменты. - person 5gon12eder; 19.06.2017

Пользовательский препроцессор является типичным подходом в генераторах парсеров/интерпретаторов, поскольку возможности макросов очень ограничены и создают потенциальные проблемы на этапе расширения, что делает отладку огромными усилиями.

Я предлагаю вам использовать проверенный временем инструмент, такой как классические программы Yacc/Lex Unix, или, если вы хотите "расширить" C, использовать C++ и Boost::spirit, генератор парсеров, который широко использует шаблоны.

person Hernán    schedule 28.03.2009
comment
Спасибо, Эрнан. Предполагая, что набор макросов подходит для задачи (как в примере), я возьму вашу точку зрения на отладку. Лексеры — это всего лишь пример, а мне нужно оставаться в сфере C. - person Remo.D; 28.03.2009
comment
Я не знал, что boost доступен для C. И оператор double ::. Я бы действительно убил, чтобы увидеть реализацию boost на C;). - person Trevor Boyd Smith; 28.03.2009
comment
Нет-нет, Boost доступен только для C++! - person Hernán; 29.03.2009