使用没有嵌套 while 循环的滑动窗口删除注释
Posted
技术标签:
【中文标题】使用没有嵌套 while 循环的滑动窗口删除注释【英文标题】:Removing comments with a sliding window without nested while loops 【发布时间】:2013-04-11 18:49:15 【问题描述】:我正在尝试使用 c 代码从 c 文件中删除 cmets 和字符串。我将坚持使用 cmets 作为示例。我有一个滑动窗口,所以在任何给定时刻我只有字符 n
和 n-1
。如果可能,我正在尝试找出一种不使用嵌套whiles
的算法,但我需要一段时间才能通过输入getchar
。我的第一个想法是在找到n=* and (n-1)=/
时找到,然后一直到n=/ and (n-1)=*
,但考虑到这是嵌套的,我觉得它效率低下。如果必须,我可以这样做,但我想知道是否有人有更好的解决方案。
【问题讨论】:
尝试制定一个状态机。 IE。当您遇到字符 '*' 或 '/' 或 '\' 或 '"' 或单引号时,您会根据之前的状态更新您的state
。(讨厌的例子可以顺便说一句。拆分评论分隔符 @987654329 @ 到多行:*\/n/
)
状态机可能是概念化这一点的最佳方式。在处理 /* foo */
样式 C cmets 时,您可能会有四种状态:normal
、normal-seen-slash
、comment
和 comment-seen-star
。
你必须处理三元组吗?您是否必须在开始注释的/
和*
之间处理反斜杠换行符(或在C++ 样式注释的/
和/
之间,或者在*
和/
之间C 风格注释的结尾)?您是否必须在 C++ 样式注释的末尾处理反斜杠换行符?您是否处理不开始注释的多字符字符常量,例如 '/*'
?显然,"/*this is not a comment*/"
不是评论;这是一个字符串,表示它不是评论。 (就像马格利特和他的“Ceci n'est pas un pipe”图片一样——谷歌一下。)
看这里:bdc.cx/software/stripcmt
【参考方案1】:
使用一个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 字符串等非注释上下文中,/*
不能正常工作;不支持//
cmets等
【讨论】:
我最终会扩展它来处理字符串、字符和 // cmets。【参考方案2】:正确地执行此操作比人们最初想象的要复杂得多,正如此处其他 cmets 巧妙地指出的那样。我强烈建议编写一个表驱动的 FSM,使用状态转换图来获得正确的转换。试图用 case 语句做更多的状态是非常容易出错的 IMO。
这是一个点/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>"];
【讨论】:
【参考方案3】:由于您只希望使用两个字符作为缓冲区并且只使用一个 while 循环,因此我建议使用第三个字符来跟踪您的状态(无论是否跳过文本)。我已经为您编写了一个测试程序,其中包含解释逻辑的内联 cmets:
// 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
【讨论】:
以上是关于使用没有嵌套 while 循环的滑动窗口删除注释的主要内容,如果未能解决你的问题,请参考以下文章
有没有不使用嵌套while循环来实现这个程序的不同方法? [复制]