Удаление комментариев со скользящим окном без вложенных циклов while

Я пытаюсь удалить комментарии и строки из файла c с кодом c. Я просто буду придерживаться комментариев к примерам. У меня скользящее окно, поэтому в любой момент у меня есть только символы n и n-1. Я пытаюсь придумать алгоритм, который не использует вложенные whiles, если это возможно, но мне понадобится один, пока getchar через ввод. Моя первая мысль состояла в том, чтобы найти, когда n=* and (n-1)=/, а затем пока через до n=/ and (n-1)=*, но, учитывая, что это вложенное время, я чувствую, что это неэффективно. Я могу сделать это таким образом, если мне нужно, но мне было интересно, есть ли у кого-нибудь лучшее решение.


person Mike Weber    schedule 18.04.2013    source источник
comment
Попробуйте сформулировать конечный автомат. т.е. всякий раз, когда вы встречаете символ '*' или '/' или '\' или '' или одинарную кавычку, вы обновляете свой state в зависимости от вашего предыдущего состояния. (Кстати, неприятные примеры могут разделить разделитель комментариев */ на несколько строк: *\/n/)   -  person Aki Suihkonen    schedule 18.04.2013
comment
Конечный автомат, вероятно, лучший способ концептуализировать это. Вероятно, у вас будет четыре состояния: normal, normal-seen-slash, comment и comment-seen-star при обработке комментариев C в стиле /* foo */.   -  person Will    schedule 18.04.2013
comment
Вам приходится иметь дело с триграфами? Нужно ли обрабатывать обратную косую черту-новую строку между / и * начального комментария (или между / и / комментария в стиле C++, или между * и / в конце комментария в стиле C)? Вам нужно обрабатывать обратную косую черту-новую строку в конце комментария в стиле С++? Вы обрабатываете многосимвольные символьные константы, такие как '/*', которые не начинают комментарий? Очевидно, что "/*this is not a comment*/" не является комментарием; это строка, говорящая, что это не комментарий. (Подобно Магритту и его картинкам Ceci n’est pas un pipe — погуглите.)   -  person Jonathan Leffler    schedule 18.04.2013
comment
Посмотрите здесь: bdc.cx/software/stripcmt   -  person Dave Jarvis    schedule 19.04.2013


Ответы (3)


Алгоритм, написанный с одним циклом while, мог бы выглядеть так:

while ((c = getchar()) != EOF)
{
    ... // looking at the byte that was just read

    if (...) // the symbol is not inside a comment
    {
        putchar(c);
    }
}

Чтобы решить, принадлежит ли ввод char комментарию, вы можете использовать конечный автомат. В следующем примере он имеет 4 состояния; также существуют правила перехода к следующему состоянию.

int state = 0;
int next_state;
while ((c = getchar()) != EOF)
{
    switch (state)
    {
        case 0: next_state = (c == '/' ? 1 : 0); break;
        case 1: next_state = (c == '*' ? 2 : c == '/' ? 1 : 0); break;
        case 2: next_state = (c == '*' ? 3 : 2); break;
        case 3: next_state = (c == '/' ? 0 : c == '*' ? 3 : 2); break;
        default: next_state = state; // will never happen
    }

    if (state == 1 && next_state == 0)
    {
        putchar('/'); // for correct output when a slash is not followed by a star
    }
    if (state == 0 && next_state == 0)
    {
        putchar(c);
    }
    state = next_state;
}

Приведенный выше пример очень прост: он не работает правильно для /* в контекстах без комментариев, например, в строках C; он не поддерживает комментарии // и т. д.

person anatolyg    schedule 18.04.2013
comment
Со временем я расширим это, чтобы делать строки, символы и // комментарии. - person Mike Weber; 19.04.2013

Сделать это правильно сложнее, чем может показаться на первый взгляд, как умело указано в других комментариях здесь. Я бы настоятельно рекомендовал написать управляемый таблицей FSM, используя диаграмму переходов состояний, чтобы получить правильные переходы. Попытка сделать что-то большее, чем несколько состояний с операторами case, ужасно подвержена ошибкам IMO.

Вот диаграмма в формате dot/graphviz, из которой вы, вероятно, могли бы напрямую закодировать таблицу состояний. Обратите внимание, что я вообще не проверял это, поэтому YMMV.

Семантика диаграммы заключается в том, что когда вы видите <ch>, это падение, если ни один из других входных данных в этом состоянии не совпадает. Конец файла является ошибкой в ​​любом состоянии, кроме S0, как и любой символ, не указанный явно, или <ch>. Печатается каждый отсканированный символ, за исключением случаев, когда он находится в комментарии (S4 и S5), и при обнаружении начального комментария (S1). Вам придется буферизовать символы при обнаружении начального комментария и распечатать их, если это фальстарт, в противном случае выбросить их, если вы уверены, что это действительно комментарий.

На точечной диаграмме sq — одинарная кавычка ', dq — двойная кавычка ".

digraph state_machine {
    rankdir=LR;
    size="8,5";

    node [shape=doublecircle]; S0 /* init */;
    node [shape=circle];

    S0  /* init */      -> S1  /* begin_cmt */ [label = "'/'"];
    S0  /* init */      -> S2  /* in_str */    [label = dq];
    S0  /* init */      -> S3  /* in_ch */     [label = sq];
    S0  /* init */      -> S0  /* init */      [label = "<ch>"];
    S1  /* begin_cmt */ -> S4  /* in_slc */    [label = "'/'"];
    S1  /* begin_cmt */ -> S5  /* in_mlc */    [label = "'*'"];
    S1  /* begin_cmt */ -> S0  /* init */      [label = "<ch>"];
    S1  /* begin_cmt */ -> S1  /* begin_cmt */ [label = "'\\n'"]; // handle "/\n/" and "/\n*"
    S2  /* in_str */    -> S0  /* init */      [label = "'\\'"];
    S2  /* in_str */    -> S6  /* str_esc */   [label = "'\\'"];
    S2  /* in_str */    -> S2  /* in_str */    [label = "<ch>"];
    S3  /* in_ch */     -> S0  /* init */      [label = sq];
    S4  /* in_slc */    -> S4  /* in_slc */    [label = "<ch>"];
    S4  /* in_slc */    -> S0  /* init */      [label = "'\\n'"];
    S5  /* in_mlc */    -> S7  /* end_mlc */   [label = "'*'"];
    S5  /* in_mlc */    -> S5  /* in_mlc */    [label = "<ch>"];
    S7  /* end_mlc */   -> S7  /* end_mlc */   [label = "'*'|'\\n'"];
    S7  /* end_mlc */   -> S0  /* init */      [label = "'/'"];
    S7  /* end_mlc */   -> S5  /* in_mlc */    [label = "<ch>"];
    S6  /* str_esc */   -> S8  /* oct */       [label = "[0-3]"];
    S6  /* str_esc */   -> S9  /* hex */       [label = "'x'"];
    S6  /* str_esc */   -> S2  /* in_str */    [label = "<ch>"];
    S8  /* oct */       -> S10 /* o1 */        [label = "[0-7]"];
    S10 /* o1 */        -> S2  /* in_str */    [label = "[0-7]"];
    S9  /* hex */       -> S11 /* h1 */        [label = hex];
    S11 /* h1 */        -> S2  /* in_str */    [label = hex];
    S3  /* in_ch */     -> S12 /* ch_esc */    [label = "'\\'"];
    S3  /* in_ch */     -> S13 /* out_ch */    [label = "<ch>"];
    S13 /* out_ch */    -> S0  /* init */      [label = sq];
    S12 /* ch_esc */    -> S3  /* in_ch */     [label = sq];
    S12 /* ch_esc */    -> S12 /* ch_esc */    [label = "<ch>"];
}
person EdF    schedule 18.04.2013

Поскольку вы хотите использовать только два символа для буфера и только один цикл while, я бы предложил третий символ для отслеживания вашего состояния (независимо от того, пропускаете ли вы текст или нет). Я собрал для вас тестовую программу со встроенными комментариями, объясняющими логику:

// Program to strip comments and strings from a C file
//
//  Build:
//     gcc -o strip-comments strip-comments.c
//
//  Test:
//     ./strip-comments strip-comments.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

/* The following is a block of strings, and comments for testing
 * the code.
 */
/* test if three comments *//* chained together */// will be removed.
static int value = 128 /* test comment within valid code *// 2;
const char * test1 = "This is a test of \" processing"; /* testing inline comment */
const char * test2 = "this is a test of \n within strings."; // testing inline comment
// this is a the last test


int strip_c_code(FILE * in, FILE * out)
{
   char      buff[2];
   char      skipping;

   skipping = '\0';
   buff[0]  = '\0';
   buff[1]  = '\0';

   // loop through the file
   while((buff[0] =  fgetc(in)) != EOF)
   {
      // checking for start of comment or string block
      if (!(skipping))
      {
         // start skipping in "//"  comments
         if ((buff[1] == '/') && (buff[0] == '/'))
            skipping = '/';

         // start skipping in "/*"  comments
         else if ((buff[1] == '/') && (buff[0] == '*'))
            skipping = '*';

         // start skipping at start of strings, but not character assignments
         else if ( ((buff[1] != '\'') && (buff[0] == '"')) &&
                   ((buff[1] != '\\') && (buff[0] == '"')) )
         {
            fputc(buff[1], out);
            skipping = '"';
         };

         // clear buffer so that processed characters are not interpreted as
         // end of skip characters.
         if ((skipping))
         {
            buff[0] = '\0';
            buff[1] = '\0';
         };
      };

      // check for characters which terminate skip block
      switch(skipping)
      {
         // if skipping "//" comments, look for new line
         case '/':
         if (buff[1] == '\n')
            skipping = '\0';
         break;

         // if skipping "/*" comments, look for "*/" terminating string
         case '*':
         if ((buff[1] == '*') && (buff[0] == '/'))
         {
            buff[0]  = '\0';
            buff[1]  = '\0';
            skipping = '\0';
         };
         break;

         // if skipping strings, look for terminating '"' character
         case '"':
         if ((buff[1] != '\\') && (buff[0] == '"'))
         {
            skipping = '\0';
            buff[0]  = '\0';
            buff[1]  = '\0';
            fprintf(out, "NULL"); // replace string with NULL
         };
         break;

         default:
         break;
      };

      // if not skipping, write character out
      if ( (!(skipping)) && ((buff[1])) )
         fputc(buff[1], out);

      // shift new character to old character position
      buff[1] = buff[0];
   };

   // verify that the comment or string was terminated properly
   if ((skipping))
   {
      fprintf(stderr, "Unterminated comment or string\n");
      return(-1);
   };

   // write last character
   fputc(buff[1], out);

   return(0);
}


int main(int argc, char * argv[])
{
   FILE * fs;

   if (argc != 2)
   {
      fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
      return(1);
   };

   if ((fs = fopen(argv[1], "r")) == NULL)
   {
      perror("fopen()");
      return(1);
   };

   strip_c_code(fs, stdout);

   fclose(fs);

   return(0);
}

/* end of source file */

Я также разместил этот код на Github, чтобы упростить загрузку и компиляцию:

https://gist.github.com/syzdek/5417109

person David M. Syzdek    schedule 18.04.2013