Shinnara's Blog
Talking with Shinnara :: NaraTalk.com

'정규표현식'에 해당되는 글 2건

  1. 2008/11/21 Perl을 이용한 C 소스 코드 분석 (2) (2)
  2. 2008/11/21 Perl을 이용한 C 소스 코드 분석 (2)

 오후에 같은 제목의 글을 올린 후, 생일 파티가 있어서 맛나게 먹고 와서 조금 더 코드를 수정하였습니다.

 이번에 추가한 부분은 리터럴을 분리해 내는 것입니다. 프로그램을 작성하는 분은 다들 아시겠지만, 주요 값들은 상수로 지정하여 사용하는 것이 일반적입니다. 프로그램을 이루는 각 모듈의 명세를 작성할 때 이러한 리터럴 상수의 종류를 구별해 놓는 것도 좋은 방법이지요. 실제로 저희 팀에서는 코드의 앞부분에 주석으로 모듈에 사용된 변수와 리터럴 상수를 모두 적어주어야 합니다. 코드가 수정되면서 갱신되지 못한 정보가 포함되는 문제가 있지만요. 그래서 제 생각에는 시간이 지남에 따라 점차 퇴색되어가는 의미없는 주석보다는 Tool을 이용해서 후처리를 해주는 것이 더 낫지 않을까 생각해보기도 합니다.

 오후에 올린 글에 이어 같은 맥락에서 코드를 추가해보겠습니다.

 open( fileHandle, $fileName) || die "Cannot open $fileName.\n";
 
 print "\n>> LITERALS \n";
 foreach  $aLine (<fileHandle> )
 {
   # delete comments
   $aLine =~ s/(\/\*).*(\*\/)//g;
   $aLine =~ s/(\/\*).*//g;  #for not ended commont with "*/"
  
   @tokens = split( /[\s\(\)\+\*\/-=;]/ , $aLine );
   foreach $token (@tokens)
   {
       $token =~ /^[A-Z][_A-Z0-9]+[A-Z0-9]$/ && ( $token =~ /^(UINT|INT)/  || print $token, "\n");
           
   }
  
 }
 close( fileHandle );

 아직은 초기 단계라 조금은 구식의 접근법이 사용되고 있는데요, 특히나 파일을 다시 여는 부분은 나중에 꼭 고쳐져야 할 부분입니다. 허나 오늘 처음 시작한 초보라는 점을 생각해주시고..^^

 앞부분의 코드는 이전 코드와 마찬가지로 주석을 제거하는 부분입니다. 그 이후의 코드에 대해 살펴보면 제일 먼저 토큰을 분리해내는 과정을 볼 수 있습니다. 컴파일러 등의 전산과 과목을 들으신 분들은 다들 아시겠지만 토큰의 분리는 파싱에 있어 가장 먼저 이루어지는 작업입니다.

@tokens = split( /[\s\(\)\+\*\/-=;]/ , $aLine );

 split 함수를 이용해서 각 줄에 있는 토큰을 분리해서 배열에 담는 과정입니다. 토큰을 분리하는데 사용하는 구분자(Deliminator)는 코드에 있는 것처럼 Whitespace, 괄호, 수학 연산자, 등호 입니다. 글을 쓰면서 생각해보니 그외에도 많은 연산자와 구분자들이 있네요.^^ 나중에 업데이트하기로 하구요..

 위의 코드에 의해 각 문장이 토큰으로 나뉘어 집니다. 그러면 이중에서 리터럴을 찾으면 되는데, 찾는 방법은 모두 대문자로 되어 있는지를 검사하는 것입니다. 저희 팀의 코딩 규칙상 리터럴 상수만 모두 대문자로 표기하도록 되어있습니다. 이를 찾는 코드는 다음과 같습니다.

$token =~ /^[A-Z][_A-Z0-9]+[A-Z0-9]$/  && ( $token =~ /^(UINT|INT)/  || print $token, "\n");

 코드를 보면 && 와 || 를 사용했는데, Perl 이야기 의 예에서 본 재밌는 표현이라 한번 써봤습니다. 해석해보면 모두 대문자와 언더바(_)로 이루어진 문자열이 있는지 확인하고, 해당 토큰이 UINT나 INT가 아니면 프린트 하도록 되어 있습니다. UINT 와 INT 는 형 변환에 쓰이는 데 리터럴처럼 인식이 되는 문제때문에 또다시 꼼수(?)를 부렸습니다.

 주먹 구구식의 코드이긴 하지만, 생각보다 흥미로운 결과를 보여주어서 재밌게 가지고 놀 수 있었습니다. 이제 퇴근 시간이 가까워 집니다. 금요일의 오후 5시 55분은 너무도 설레이는 시간이지요. 모두들 좋은 주말 되세요.~ 다음 주에 다시 오겠습니다.








0 Trackback, 2 Comment

TRACKBACK :: http://naratalk.com/trackback/264 관련글 쓰기

댓글을 달아 주세요

  1. BlogIcon Shinnara  댓글주소  수정/삭제  댓글쓰기

    split 하는 부분에 deliminator를 조금 더 추가하였습니다.

    @tokens = split( /[\s\(\)\[\]\+\*\/-=;,&]/ , $aLine );

    여러 코드에 대해 테스트를 해보니 아직 많이 수정 보완해야겠네요^^

    모두들 즐거운 주말 되시길..

    2008/11/22 10:14
  2. BlogIcon Shinnara  댓글주소  수정/삭제  댓글쓰기

    Perl 과 관련하여 재밌는 행사가 있었네요. 조금만 빨리 Perl을 알았어도 좋았을 것을.. 관련 후기를 보고 알았네요. http://iklo.egloos.com/4570027

    2008/11/22 10:22


 아침에 Perl의 마수에 걸려들었다는 글을 썼는데요, 오후에도 그 마수에 빠져 열심히 허우적거리고 있습니다. 본래 Perl을 알아보게 된 계기가 팀에서 만들고 있는 프로그램 소스 코드를 분석해야 해서 문자열 처리가 쉬운 언어를 찾는 것이었습니다. 그래서 Perl의 기초를 읽어가면서 테스트 프로그램을 짜보았습니다. Perl 이야기를 무척이나 재미있게 읽었는데, 처음 Perl을 접하시는 분들에게는 많은 도움이 될 것입니다.

 먼저 하고자 하는 것을 간략히 소개하자면
  • 소스 코드 내에 존재하는 글로벌 변수와 리터럴 상수를 추출
  • 소스 코드에 사용된 함수 이름 (Function Call)
입니다. 위의 정보를 이용해서 해당 모듈( 저희 팀의 코딩 규칙은 하나의 c 파일에 하나의 함수만을 포함합니다)에 대한 명세를 만들어 내는 것이 소스 코드 분석의 목적입니다.

 소스 코드에 대한 분석 툴이 다양한 것으로 알고있는데, 특히 Doxygen 같은 툴을 쓰면 Call Graph까지 그려준다고 들은 바 있습니다. 아직 제대로 사용해본 적은 없습니다. 지난 번에 잠깐 써 봤는데, 생각보다 쉽지 않더군요.

 또한, 소스 코드를 분석하는 방법은 크게 두가지
  • lex, yacc 같은 parsing 툴을 이용하는 방법
  • 직접 parsing 로직을 구현하는 방법
으로 생각해보았습니다. 첫번째 방법의 경우, 관련 분야의 전문적인 Tool이라 분명 멋진 결과를 얻어 낼 수 있을 테지만, 문제는 제가 아직 그 툴에 대해 잘 모른다는 것입니다. 제대로 쓰는 데까지 꽤 많은 시간이 걸릴 것 같아 일단은 선택에서 제외하였습니다.

 남은 방법은 직접 로직을 구현하는 것인데, 이 때부터 어떤 방식으로 구현할 지를 고민하게 되었습니다. 제게 있어 가장 손쉽게 쓸수 있는 언어는 Java입니다. 좋아하기도 하고 많이 쓰기도 했죠. 하지만 파싱할 생각을 하니 그리 만만치는 않더군요. Java에서도 Regular Expression을 쓸 수 있지만, 지난 몇번의 시도에서 RE를 제대로 쓰지 못한 기억 때문인지 선뜻 Java를 선택하기 어려웠습니다. 그래서 아침에 Google 의 도움을 받았는데, 앞의 글에서 처럼 Perl의 마수에 제대로 걸려든 것이지요.

 그래서 먼저 Perl을 배워볼 겸 해서 관련 문서를 뒤적이고 인터넷 강좌를 열심히 읽었습니다. 그래봐야 1~2시간이지만요. 새로운 언어를 배우는 것이라 시간이 걸리는 것은 lex,yacc 같은 툴을 배우는 것과 별반 차이가 없을 것 같지만 그래도 툴이 아닌 새로운 언어라는게 더 매력적이라고 혼자서 위로한답니다. ^^

 하여간, 제일 먼저 작성해본 것은 function의 이름을 찾는 것입니다. 오늘 작성한 코드는 가장 기본적인 단계인 call 하는 function의 이름을 순차적으로 console에 찍는 것입니다. 좀더 나아간다면 중복되는 함수를 제거하고 깔끔하게 출력을 해주어야 겠지만 이 정도만 되어도 오늘은 충분히 만족합니다.^^

 #!/usr/bin/perl
# mycat2.pl

if ( $#ARGV < 0 )
 { die "no input file name.\n"; }
if ( $#ARGV > 0 )
 { die "Too many input file.\n"; }
 

$fileName = shift(@ARGV);

if( -d $fileName )
 {die "$fileName is a directory.\n"}
 
 -e $fileName || die "$fileName is not exist.\n";
 
 -T $fileName || die "$fileName is not a text file.\n";
 
 open( fileHandle, $fileName) || die "Cannot open $fileName.\n";
 
 #@allLines = <fileHandle>;
 #close(fileHandle);
 #print @allLines;
 
 foreach  $aLine (<fileHandle> )
 {
   # delete comments
   $aLine =~ s/(\/\*).*(\*\/)//g;
   $aLine =~ s/(\/\*).*//g;  #for not ended commont with "*/"
  
   # show function call
   if( $aLine =~ /([\w\d])+\s*\(.*\)/ ) {
      unless ($& =~ /^(if|switch|sizeof|for|while)/ )
      {
        $& =~ /\s*\(/;
        print "$` \n";
      }
   }
 }
 close( fileHandle );
 코드의 앞부분에 나오는 파일 체크하는 부분은 앞에서 말한 Perl 이야기의 강좌 부분을 참고하였습니다. 위의 코드를 실행시키면 해당 모듈 내에서 호출하는 함수의 이름이 나오게 됩니다. 좀더 자세히 코드를 살펴보겠습니다.

   # delete comments
   $aLine =~ s/(\/\*).*(\*\/)//g;
   $aLine =~ s/(\/\*).*//g;  #for not ended commont with "*/"

 위의 두 라인은 문서 내에 존재하는 주석을 제거하는 부분입니다. $aLine은 foreach 문장에서 fileHandle로 부터 1줄씩 가져오게됩니다. 따라서 처리의 기준이 1 문장입니다. 첫번째 치환문은 /* 로 시작해서 */로 끝나는 부분을 공백으로 치환합니다. 이를 통해서도 제거 되지 않는 주석이 있는데, */ 로 끝나지 않은 주석은 여전히 남게 됩니다. 그래서 아래줄을 이용해서 그러한 주석도 제거합니다. 이 경우 아래와 같은 경우에서 문제가 생길 수 있습니다.

/* this is a
    comment for you */

첫째줄의 /* this is a 는 공백으로 치환되지만, 둘째 줄의 comm... */ 은 그대로 남게 되겠지요. 하지만 팀의 코딩 Standard에서는 여러줄에 걸치 Comment 는 무조건 앞부분에 /* 를 붙이도록 되어 있어 위와 같은 문제점은 발생하지 않습니다. 그래도 범용(?)적인 처리를 위해서라면 아래와 같은 코드를 추가할 수 도 있겠네요.

$aLine =~ s/.*\*\)//g;

이 문장을 뒷부분에 추가시키면 앞의 두 구문에 의해 제거되지 않은 주석도 제거가 되겠지요? (해보지 않았습니다 ^^)

if( $aLine =~ /([\w\d])+\s*\(.*\)/ ) {


 위 if 문은 함수 호출의 형태로 쓰여진 부분을 찾습니다. 즉 알파벳과 숫자로 쓰여진 부분과 괄호로 여닫힌 부분으로 이루어진 문자열을 찾습니다. 중간에 \s* 가 들어간 것은 소스 코드를 작성하는 과정에서 함수 이름과 괄호 사이에 공백이 들어가는 경우도 있기 때문에 이를 위해서 삽입했습니다. 위와 같은 검색의 경우 몇가지 문제가 발생합니다.
 
 첫째, 현재의 처리 단위가 1줄이기때문에 함수 호출이 두줄 이상에 걸쳐진 경우 위의 조건식으로 검사가 안됩니다. 닫는 괄호 부분을 삭제하면 일정 부분 문제가 줄어들겠지요.
 둘째, if(조건문) 과 같이 마치 실제로는 함수의 호출이 아닌 경우가 존재합니다.

첫번째 문제는 다음으로 미루기로 하고, 남은 문제의 해결을 위해 아래와 같은 꼼수(?)를 부렸습니다.

unless ($& =~ /^(if|switch|sizeof|for|while)/ )

if,switch 등과 같은 예약어(?)로 시작되지 않는 경우에 한해서 다음 처리를 하도록 한  것입니다. 현재는 위와 같은 정도면 대강 처리가 가능하더군요.

마지막으로 실제 함수 이름을 찾아내서 찍어주는 부분입니다.

$& =~ /\s*\(/;
print "$` \n";

원리는 간단하죠? 즉, 함수 호출에서 인자가 쓰이는 괄호 앞부분을 찾아내어 출력하게 됩니다.

아직 가야할 길이 멀겠지만, 시작이 반이라 했으니 이제 반만 더 가면 되겠지요?

앞으로 계속해서 관련 내용을 올리도록 하겠습니다.

0 Trackback, 2 Comment

TRACKBACK :: http://naratalk.com/trackback/263 관련글 쓰기

댓글을 달아 주세요

  1. Tony  댓글주소  수정/삭제  댓글쓰기

    펄 좋죠 ^^ language for human

    2008/11/21 15:37
    • BlogIcon Shinnara  댓글주소  수정/삭제

      오홋.. 이렇게나 빨리 글을 읽으시다니.. ^^ 글 쓰고 나서 다시 읽고 있는 중이었답니다~~ Perl 좋은 것 같아요. 빨리 배우고 싶답니다~~~

      2008/11/21 15:40

1 
다...... (264)
Computer/Programming (106)
Links (14)
책 읽는 즐거움 (7)
끄적임 (66)
즐거운 과학 나라 (7)
일본 (5)
Study (4)