2013-04-18 2 views
4

c 코드로 c 파일에서 주석과 문자열을 제거하려고합니다. 예제에 대한 코멘트 만 붙이겠습니다. 슬라이딩 윈도우가있어 주어진 순간에 nn-1 문자 만 있습니다. 가능한 경우 중첩 된 whiles을 사용하지 않는 알고리즘을 알아 내려고하지만 입력을 통해 getchar을 사용해야합니다. 처음 생각했을 때 n=* and (n-1)=/을 통해 n=/ and (n-1)=*까지 읽었을 때 찾아 냈지만 이것이 비효율적이라고 느낀 점을 감안할 때 고려했습니다. 그렇게해야한다면이 방법을 사용할 수 있습니다.하지만 누구에게 더 나은 솔루션이 있는지 궁금합니다.중첩 된 루프가없는 슬라이딩 윈도우로 주석 제거

+4

나는 인라인 코멘트 논리를 설명과 함께 당신을 위해 함께 테스트 프로그램을 넣었습니다 상태 기계를 공식화하십시오. 나는. 문자 '*'또는 '/'또는 '\'또는 '' '또는 작은 따옴표를 만나면 이전 상태에 따라'상태 '를 업데이트합니다 (불쾌한 예는 btw입니다.주석 행 분리기'* /'를 여러 줄로 나누십시오 :'* \/n /') –

+0

상태 머신은 이것을 개념화하는 가장 좋은 방법입니다. '/ * foo * /'스타일 C 주석을 처리 할 때는'normal','normal-seen-slash','comment','comment-seen-star'의 네 가지 상태가있을 것입니다. – Will

+7

trigraphs를 다뤄야합니까? 시작 주석의'/'와'* '사이에 (또는 C++ 스타일 주석의'/'와'/'사이에 또는'*'와'/'사이에 백 슬래시 - 개행 문자를 처리해야합니까? C 스타일 주석의 끝)? C++ 스타일 주석 끝에 백 슬래시 - 개행 문자를 처리해야합니까? 주석을 시작하지 않는 '/ *'와 같은 다중 문자 상수를 처리합니까? 분명히, "/ * 이것은 주석이 아닙니다. * /"'는 주석이 아닙니다; 그것은 주석이 아니라는 것을 말하는 문자열입니다. (오히려 Magritte와 그의 "Ceci n'est pas un pipe"그림 - Google it.) –

답변

2

과 같을 수 하나 개 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 문자열처럼 주석이 아닌 상황에서 /* 제대로 작동하지 않습니다; // 개의 댓글 등을 지원하지 않습니다.

+0

나는 이것을 문자열, 문자 // 주석도 포함합니다. –

2

이 작업을 올바르게 수행하는 것은 처음에는 생각보다 복잡 할 수 있습니다. 다른 의견에 의해 지적되었습니다. 전이를 올바르게하기 위해 상태 전이 다이어그램을 사용하여 테이블 중심 FSM을 작성하는 것이 좋습니다. 사례 진술과 함께 몇 가지 상태 이상을 시도하는 것은 오류가 발생하기 쉬운 IMO입니다.

다음은 상태 표를 직접 코드화 할 수있는 도트/그래프 형식의 다이어그램입니다. 저는 이것을 전혀 테스트하지 않았 음을 유의하십시오, 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>"]; 
} 
1

만 버퍼에 대한 두 개의 문자를 사용하고자하는 단 하나의 루프 동안, 나는 당신의 상태를 추적 할 수있는 세 번째 문자 제안하기 때문에 (여부를 텍스트를 건너 뛰는 여부). 다운로드하고 컴파일 나는 또한 그것을 쉽게하기 위해 Github에서에이 코드를 게시 한

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

:

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