使用没有嵌套 while 循环的滑动窗口删除注释

Posted

技术标签:

【中文标题】使用没有嵌套 while 循环的滑动窗口删除注释【英文标题】:Removing comments with a sliding window without nested while loops 【发布时间】:2013-04-11 18:49:15 【问题描述】:

我正在尝试使用 c 代码从 c 文件中删除 cmets 和字符串。我将坚持使用 cmets 作为示例。我有一个滑动窗口,所以在任何给定时刻我只有字符 nn-1。如果可能,我正在尝试找出一种不使用嵌套whiles 的算法,但我需要一段时间才能通过输入getchar。我的第一个想法是在找到n=* and (n-1)=/ 时找到,然后一直到n=/ and (n-1)=*,但考虑到这是嵌套的,我觉得它效率低下。如果必须,我可以这样做,但我想知道是否有人有更好的解决方案。

【问题讨论】:

尝试制定一个状态机。 IE。当您遇到字符 '*' 或 '/' 或 '\' 或 '"' 或单引号时,您会根据之前的状态更新您的 state。(讨厌的例子可以顺便说一句。拆分评论分隔符 @987654329 @ 到多行:*\/n/) 状态机可能是概念化这一点的最佳方式。在处理 /* foo */ 样式 C cmets 时,您可能会有四种状态:normalnormal-seen-slashcommentcomment-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> 之外的任何状态下的文件结尾都是错误。除了在注释中(S4S5)和检测到开始注释时(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 循环的滑动窗口删除注释的主要内容,如果未能解决你的问题,请参考以下文章

UIScrollView嵌套TableView手势冲突问题

如何实现滑动窗口或减少这些嵌套循环?

Python基础语法

有没有不使用嵌套while循环来实现这个程序的不同方法? [复制]

NestedScrollView嵌套RecycleView 滑动 实现上滑隐藏 下滑显示头部效果

在 KDB/Q 中按时滑动窗口