Я пытаюсь удалить комментарии и строки из файла c с кодом c. Я просто буду придерживаться комментариев к примерам. У меня скользящее окно, поэтому в любой момент у меня есть только символы n
и n-1
. Я пытаюсь придумать алгоритм, который не использует вложенные whiles
, если это возможно, но мне понадобится один, пока getchar
через ввод. Моя первая мысль состояла в том, чтобы найти, когда n=* and (n-1)=/
, а затем пока через до n=/ and (n-1)=*
, но, учитывая, что это вложенное время, я чувствую, что это неэффективно. Я могу сделать это таким образом, если мне нужно, но мне было интересно, есть ли у кого-нибудь лучшее решение.
Удаление комментариев со скользящим окном без вложенных циклов while
Ответы (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; он не поддерживает комментарии //
и т. д.
Сделать это правильно сложнее, чем может показаться на первый взгляд, как умело указано в других комментариях здесь. Я бы настоятельно рекомендовал написать управляемый таблицей 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>"];
}
Поскольку вы хотите использовать только два символа для буфера и только один цикл 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
state
в зависимости от вашего предыдущего состояния. (Кстати, неприятные примеры могут разделить разделитель комментариев*/
на несколько строк:*\/n/
) - person Aki Suihkonen   schedule 18.04.2013normal
,normal-seen-slash
,comment
иcomment-seen-star
при обработке комментариев C в стиле/* foo */
. - person Will   schedule 18.04.2013/
и*
начального комментария (или между/
и/
комментария в стиле C++, или между*
и/
в конце комментария в стиле C)? Вам нужно обрабатывать обратную косую черту-новую строку в конце комментария в стиле С++? Вы обрабатываете многосимвольные символьные константы, такие как'/*'
, которые не начинают комментарий? Очевидно, что"/*this is not a comment*/"
не является комментарием; это строка, говорящая, что это не комментарий. (Подобно Магритту и его картинкам Ceci n’est pas un pipe — погуглите.) - person Jonathan Leffler   schedule 18.04.2013