Стиль C: Макросы или препроцессор? - PullRequest
4 голосов
/ 28 марта 2009

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

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

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

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

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

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

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

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

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

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

/**** 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.

Ответы [ 2 ]

6 голосов
/ 28 марта 2009

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

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

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

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

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

3 голосов
/ 28 марта 2009

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

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

...