. 스타일

다음은 CParseCpp 클래스의 구현 소스이다. 분석 동작이 다소 복잡하기 때문에 길이가 조금 길다.

 

enum {

     CPP_CON_NORMAL,

     CPP_CON_BLOCKCOMMENT,

     CPP_CON_LINECOMMENT,

     CPP_CON_STRING,

     CPP_CON_CHAR

};

 

enum {

     CPP_STYLE_NORMAL,

     CPP_STYLE_COMMENT,

     CPP_STYLE_STRING,

     CPP_STYLE_CHAR,

     CPP_STYLE_NUMBER,

     CPP_STYLE_KEYWORD,

     CPP_STYLE_PREPROCESSOR

};

 

CParseCpp::CParseCpp()

{

     lstrcpy(arStyle[0].name,"보통");

     arStyle[0].fore=(DWORD)-1;

     lstrcpy(arStyle[1].name,"주석");

     arStyle[1].fore=RGB(0,0,0);

     arStyle[1].back=RGB(192,192,192);

     lstrcpy(arStyle[2].name,"문자열");

     arStyle[2].fore=RGB(0,128,0);

     lstrcpy(arStyle[3].name,"문자");

     arStyle[3].fore=RGB(0,128,0);;

     lstrcpy(arStyle[4].name,"숫자");

     arStyle[4].fore=RGB(0,0,255);

     lstrcpy(arStyle[5].name,"키워드");

     arStyle[5].fore=RGB(255,0,0);

     lstrcpy(arStyle[6].name,"전처리기");

     arStyle[6].fore=RGB(255,0,255);

}

 

TCHAR *CParseCpp::GetInfo(int iIndex)

{

     switch (iIndex) {

     case 0:

          return (TCHAR *)1;

     case 1:

          return TEXT(" \t\r\n\"\’\\.,<>:;/()[]{}~!@#%^&*-+=?|");

     case 2:

          return TEXT("//");

     case 3:

          return TEXT("/*");

     case 4:

          return TEXT("*/");

     case 5:

          return TEXT("{");

     case 6:

          return TEXT("}");

     }

     return 0;

}

 

void CParseCpp::ParseLine(CApiEdit &ae,int nLine)

{

     DWORD Context;

     int s,e,i;

     int nUnit=0;

     int idpos,idend;

 

     if (pInfo[nLine].pUnit[0].pos != -1)

          return;

 

     if (nLine == 0) {

          Context=CPP_CON_NORMAL;

     } else {

          Context=pInfo[nLine-1].Context;

     }

 

     s=ae.pLine[nLine].Start;

     e=ae.pLine[nLine].End;

     idpos=s;

 

     switch(Context) {

     case CPP_CON_NORMAL:

          MakeParseInfo(nLine,nUnit,s,CPP_STYLE_NORMAL);

          break;

     case CPP_CON_STRING:

          MakeParseInfo(nLine,nUnit,s,CPP_STYLE_STRING);

          break;

     case CPP_CON_CHAR:

          MakeParseInfo(nLine,nUnit,s,CPP_STYLE_CHAR);

          break;

     case CPP_CON_BLOCKCOMMENT:

          MakeParseInfo(nLine,nUnit,s,CPP_STYLE_COMMENT);

          break;

     case CPP_CON_LINECOMMENT:

          if (ae.pLine[nLine].nLine != 0) {

              MakeParseInfo(nLine,nUnit,s,CPP_STYLE_COMMENT);

              goto EndParse;

          } else {

              MakeParseInfo(nLine,nUnit,s,CPP_STYLE_NORMAL);

              Context=CPP_CON_NORMAL;

              break;

          }

     }

 

     for (i=s;i<e;i++) {

          switch(Context) {

          case CPP_CON_NORMAL:

              if (ae.buf[i]==‘/’ && ae.buf[i+1]==‘*’) {

                   Context=CPP_CON_BLOCKCOMMENT;

              }

              if (ae.buf[i]==‘/’ && ae.buf[i+1]==‘/’) {

                   Context=CPP_CON_LINECOMMENT;

              }

              if (ae.buf[i]==‘\"‘) {

                   Context=CPP_CON_STRING;

              }

              if (ae.buf[i]==‘\’’) {

                   Context=CPP_CON_CHAR;

              }

 

              if (Context!=CPP_CON_NORMAL || strchr(GetInfo(1),ae.buf[i]) || i==e-1) {

                   if (i==e-1 && !strchr(GetInfo(1),ae.buf[i])) {

                        idend=i;

                   } else {

                        idend=i-1;

                   }

 

                   if (idend-idpos+1 >= 1) {

                        if (IsKeyword(ae,idpos,idend)) {

                             MakeParseInfo(nLine,nUnit,idpos,CPP_STYLE_KEYWORD);

                             MakeParseInfo(nLine,nUnit,idend+1,CPP_STYLE_NORMAL);

                        } else

                        if (IsNumber(ae,idpos,idend)) {

                             MakeParseInfo(nLine,nUnit,idpos,CPP_STYLE_NUMBER);

                             MakeParseInfo(nLine,nUnit,idend+1,CPP_STYLE_NORMAL);

                        } else

                        if (idpos!=s && ae.buf[idpos-1]==‘#’) {

                             if (IsPreProcessor(ae,idpos-1,idend)) {

                                 MakeParseInfo(nLine,nUnit,idpos-1,CPP_STYLE_PREPROCESSOR);

                                 MakeParseInfo(nLine,nUnit,idend+1,CPP_STYLE_NORMAL);

                             }

                        }

                   }

 

                   idpos=i+1;

              }

 

              switch (Context) {

              case CPP_CON_NORMAL:

                   break;

              case CPP_CON_BLOCKCOMMENT:

                   MakeParseInfo(nLine,nUnit,i,CPP_STYLE_COMMENT);

                   i++;

                   break;

              case CPP_CON_LINECOMMENT:

                   MakeParseInfo(nLine,nUnit,i,CPP_STYLE_COMMENT);

                   goto EndParse;

              case CPP_CON_STRING:

                   MakeParseInfo(nLine,nUnit,i,CPP_STYLE_STRING);

                   break;

              case CPP_CON_CHAR:

                   MakeParseInfo(nLine,nUnit,i,CPP_STYLE_CHAR);

                   break;

              }

              continue;

          case CPP_CON_BLOCKCOMMENT:

              if (ae.buf[i]==‘*’ && ae.buf[i+1]==‘/’) {

                   i++;

                   MakeParseInfo(nLine,nUnit,i+1,CPP_STYLE_NORMAL);

                   Context=CPP_CON_NORMAL;

                   idpos=i+1;

              }

              continue;

          case CPP_CON_STRING:

              if (ae.buf[i]==‘\\’) {

                   i++;

                   continue;

              }

              if (ae.buf[i]==‘\"‘) {

                   MakeParseInfo(nLine,nUnit,i+1,CPP_STYLE_NORMAL);

                   Context=CPP_CON_NORMAL;

                   idpos=i+1;

              }

              continue;

          case CPP_CON_CHAR:

              if (ae.buf[i]==‘\\’) {

                   i++;

                   continue;

              }

              if (ae.buf[i]==‘\’’) {

                   MakeParseInfo(nLine,nUnit,i+1,CPP_STYLE_NORMAL);

                   Context=CPP_CON_NORMAL;

                   idpos=i+1;

              }

              continue;

          }

     }

 

     if (Context==CPP_CON_STRING || Context==CPP_CON_CHAR) {

          if (ae.buf[e]==‘\r’ && ae.buf[e-1]!=‘\\’) {

              Context=CPP_CON_NORMAL;

          }

     }

 

EndParse:

     pInfo[nLine].Context=Context;

}

 

BOOL CParseCpp::IsKeyword(CApiEdit &ae,int s, int e)

{

     static TCHAR *keyword=" auto break bool case char class const "

          "continue default delete do double else explicit enum "

          "extern float for friend goto if inline int long "

          "new namespace operator private protected public register "

          "return short signed sizeof static struct switch "

          "template this try typedef union unsigned using "

          "virtual void volatile while __asm __fastcall __based "

          "__cdecl __pascal __inline ";

 

     return IsStringExist(keyword,ae.buf+s,e-s+1,TRUE);

}

 

BOOL CParseCpp::IsPreProcessor(CApiEdit &ae,int s, int e)

{

     static TCHAR *preproc=" #define #elif #else #endif #error #if #ifdef "

          "#ifndef #include #line #pragma #undef ";

 

     return IsStringExist(preproc,ae.buf+s,e-s+1,TRUE);

}

 

C 소스는 공백, 명칭, 키워드, 구두점, 연산자, 상수, 주석 등의 7가지 요소로 구성된다. 이런 문법적인 구분대로 소스를 정확하게 해석하려면 거의 컴파일러 수준의 코드가 필요하다. 명칭과 일반 문자열을 구분하려면 변수 선언문이나 함수 원형을 해석해야 하므로 구문 강조 정도로는 어림도 없다. 뿐만 아니라 문법대로 정확하게 요소를 나눌 필요도 없는데 공백에 스타일을 주는 것은 실질적으로 의미가 없으며 연산자나 구두점까지 강조할 필요는 없는 것이다.

그래서 여기서는 언어의 문법적인 기준보다 형태적인 기준에 의해 스타일을 부여하기로 한다. C 소스는 다음 일곱 가지 스타일로 구분하며 각 스타일은 고유의 색상으로 출력할 수 있다.

 

스타일

설명

보통 문자열

명칭, 구두점을 포함한 일반적인 문자열이다.

주석

/* */ 둘러싸인 블록 주석과 // 시작되는 주석이다.

문자열 상수

겹따옴표로 둘러싸인 문자열이다.

문자 상수

홑따옴표로 둘러싸인 문자열이다.

숫자

아라비아 숫자 또는 16진수로 숫자 상수이다.

키워드

C 언어의 예약어이다.

전처리기

#include 등의 특별한 전처리 명령이다.

 

이 분류는 절대적인 것은 아니며 분석기 제작자에 따라서 조금씩 달라질 수도 있다. 예를 들어 문자열 상수와 문자 상수는 하나의 스타일로 통합할 수 있으며 명칭이나 구두점을 별도의 스타일로 분할할 수도 있다. 여기서는 이 분류대로 스타일을 정의할 것이다.

분석기는 스타일 외에도 이전 줄의 분석결과를 저장하기 위한 컨텍스트값을 정의한다. C 언어는 다음 줄까지 이어지는 스타일로 주석과 문자() 상수를 가지고 있으므로 일반 컨텍스트를 포함하여 총 7개의 컨텍스트값을 정의한다. 블록 주석과 한 줄 주석은 같은 주석 스타일이지만 해석 방법이 다르기 때문에 두 개의 컨텍스트로 분리한다. 스타일값과 컨텍스트값은 단순한 정수이므로 열거형으로 선언하였다.

생성자는 스타일의 색상인 arStyle 배열을 초기화한다. 각 스타일별로 이름과 색상값을 대입하였다. 이 코드를 보면 주석은 회색 바탕에 검정색이며 문자열은 기본 바탕색에 연두색이라는 것을 알 수 있다. 생성자에서 정의하는 arStyle의 초기값은 분석기의 디폴트 색상이 되는데 차후에 설정 기능을 통해 사용자가 변경할 수도 있다.

GetInfo 함수의 구조는 무척 간단하다. 분석기 ID로는 1을 리턴하는데 TCHAR *로 캐스팅해서 리턴해야 한다. GetInfo(0)을 호출하는 쪽에서는 이 값을 다시 int로 캐스팅한 후 비교해야 할 것이다. 인수로 전달되는 인덱스에 따라 C 언어에서 사용되는 구분자 목록과 한 줄 주석, 블록 주석 기호들을 리턴하고 있다. 이 값들은 호스트가 분석기별로 다른 처리를 하고자 할 때 사용된다.

ParseLine 함수는 한 줄의 문법을 분석하기 위해 몇 개의 도우미 함수를 호출한다. 대표적으로 IsKeyword 함수를 분석해보자. 이 함수는 keyword 포인터에 C 언어의 키워드에 해당하는 단어의 목록을 저장해놓고 있다. 주의할 것은 이런 상수 문자열 포인터는 반드시 static으로 선언하여 전역 메모리에서 한 번만 초기화하도록 해야 한다는 점이다. 만약 이 배열을 지역변수로 선언한다면 함수가 호출될 때마다 스택에 초기화되므로 아주 느려진다. 더구나 이 함수는 반복적으로 자주 호출되기 때문에 속도 저하가 아주 뚜렷하게 나타난다.

IsKeyword 함수는 ApiEdit의 레퍼런스 ae와 조사할 문자열의 시작위치 s, 끝 위치 e를 전달받아 buf[s]~buf[e]까지의 문자열이 C 언어의 키워드인지 조사한다. 조사할 문자열이 keyword 가 가리키는 문자열 목록 중 하나인지 검사해 그 결과를 리턴하면 된다. CParse에 문자열 목록을 검색하는 IsStringExist 함수가 이미 작성되어 있으므로 이 함수만 호출하면 오프셋 s~e사이에 있는 문자열이 키워드인지 아닌지 알아낼 수 있다. C 언어는 대소문자를 구분하므로 bCase 인수는 TRUE로 전달해야 한다.

IsPreProcessor 함수도 키워드 비교 함수와 완전히 동일하게 작성되어 있다. 다만 전처리기 목록을 가지는 preproc 문자열 상수가 다르게 작성되어 있을 뿐이다. 이후 다른 분석기에서도 태그, 속성, 연산자 등의 목록을 검색할 때 동일한 방법으로 함수를 작성하면 된다. 목록을 구성하는 문자열 상수를 만든 후 이 상수와 검색할 문자열을 IsStringExist로 넘겨 주기만 하면 된다. 이때 검색 속도를 향상시키려면 가급적 자주 나오는 문자열을 앞쪽에 배치하면 좋다.