. SQL 분석기

다음은 SQL 분석기를 작성해보자. CParseSql 클래스는 다음과 같이 선언된다.

 

class CParseSql : public CParse

{

public:

     CParseSql();

     ~CParseSql() {};

     TCHAR *GetInfo(int iIndex);

 

private:

     void ParseLine(CApiEdit &ae,int nLine);

     BOOL IsKeyword(CApiEdit &ae,int s, int e);

     BOOL IsType(CApiEdit &ae,int s, int e);

};

 

앞서 작성한 두 개의 분석기와 거의 비슷하다. C 언어에 대한 SQL 문법의 특징을 간단하게 요약해보면 다음과 같다.

 

한 줄 주석이 --로 되어 있다.

프리프로세스 스타일 대신 타입 스타일이 필요하다.

문자열 표현은 겹따옴표 대신 홑따옴표가 사용된다.

확장열을 표현하는 방법이 다르다. 따옴표 자체는 두 개의 연속적인 따옴표로 표현한다.

엔터 기호에 의해 문자열이 종료되지 않는다.

 

이런 차이점에 의해 ParseLine 함수가 달라지게 된다. 클래스의 구현 코드는 다음과 같다. 코드 리스트 이상의 설명은 필요치 않을 것으로 생각된다.

 

enum {

     SQL_CON_NORMAL,

     SQL_CON_BLOCKCOMMENT,

     SQL_CON_LINECOMMENT,

     SQL_CON_STRING

};

 

enum {

     SQL_STYLE_NORMAL,

     SQL_STYLE_COMMENT,

     SQL_STYLE_STRING,

     SQL_STYLE_NUMBER,

     SQL_STYLE_KEYWORD,

     SQL_STYLE_TYPE

};

 

CParseSql::CParseSql()

{

     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(255,0,0);

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

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

     lstrcpy(arStyle[5].name,"타입");

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

}

 

TCHAR *CParseSql::GetInfo(int iIndex)

{

     switch (iIndex) {

     case 0:

          return (TCHAR *)3;

     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 CParseSql::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=SQL_CON_NORMAL;

     } else {

          Context=pInfo[nLine-1].Context;

     }

 

     s=ae.pLine[nLine].Start;

     e=ae.pLine[nLine].End;

     idpos=s;

 

     switch(Context) {

     case SQL_CON_NORMAL:

          MakeParseInfo(nLine,nUnit,s,SQL_STYLE_NORMAL);

          break;

     case SQL_CON_STRING:

          MakeParseInfo(nLine,nUnit,s,SQL_STYLE_STRING);

          break;

     case SQL_CON_BLOCKCOMMENT:

          MakeParseInfo(nLine,nUnit,s,SQL_STYLE_COMMENT);

          break;

     case SQL_CON_LINECOMMENT:

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

              MakeParseInfo(nLine,nUnit,s,SQL_STYLE_COMMENT);

              goto EndParse;

          } else {

              MakeParseInfo(nLine,nUnit,s,SQL_STYLE_NORMAL);

              Context=SQL_CON_NORMAL;

              break;

          }

     }

 

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

          switch(Context) {

          case SQL_CON_NORMAL:

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

                   MakeParseInfo(nLine,nUnit,i,SQL_STYLE_COMMENT);

                   i++;

                   Context=SQL_CON_BLOCKCOMMENT;

                   continue;

              }

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

                   MakeParseInfo(nLine,nUnit,i,SQL_STYLE_COMMENT);

                   Context=SQL_CON_LINECOMMENT;

                   goto EndParse;

              }

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

                   MakeParseInfo(nLine,nUnit,i,SQL_STYLE_STRING);

                   Context=SQL_CON_STRING;

                   continue;

              }

 

              if (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,SQL_STYLE_KEYWORD);

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

                        } else

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

                            MakeParseInfo(nLine,nUnit,idpos,SQL_STYLE_NUMBER);

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

                        } else

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

                             MakeParseInfo(nLine,nUnit,idpos,SQL_STYLE_TYPE);

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

                        }

                   }

 

                   idpos=i+1;

              }

              continue;

          case SQL_CON_BLOCKCOMMENT:

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

                   i++;

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

                   Context=SQL_CON_NORMAL;

                   idpos=i+1;

              }

              continue;

          case SQL_CON_STRING:

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

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

                        i++;

                        continue;

                   } else {

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

                        Context=SQL_CON_NORMAL;

                        idpos=i+1;

                   }

              }

              continue;

          }

     }

 

EndParse:

     pInfo[nLine].Context=Context;

}

 

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

{

     static TCHAR *keyword="abs acos all alter and any ascii "

          "asin atan atn2 avg backup begin between break case "

          "cast ceiling charindex checkpoint checksum checksum_agg "

          "close coalesce collate collationproterty col_length "

          "col_name columnproperty commit constraint convert contains containstable "

          "continue cos cot count count_big create current_timestamp "

          "current_user cursor cursor_status database databaseproperty "

          "databasepropertyex datalength dateadd datediff datename "

          "datepart day db_id db_name dbcc deallocate declare "

          "default degrees delete deny difference drop dump else "

          "end execute exists exp fetch file_id file_name "

          "filegroup_id filegroup_name filegroupproperty fileproperty "

          "floor formatmessage freetext freetexttable from function "

          "getansinull getdate getutcdate go goto grant group "

          "grouping having if in index insert isdate isnull "

          "isnumeric key kill left len like load log log10 lower "

          "ltrim max min month nchar newid not null nullif numeric "

          "open or order parsename patindex permissions pi power primary "

          "print procedure quotename radians raiserror rand readtext "

          "reconfigure replace replicate restore return reverse revoke "

          "right rollback round rtrim rule save schema select set "

          "setuser shutdown sign sin some soundex space square sqrt "

          "statistics stdev stdevp str stuff substring sum table "

          "tan textptr textvalid transaction trigger trigger_nestlevel "

          "truncate unicode union update updatetext upper use user "

          "var varp view waitfor where while work writetext year "  ;

 

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

}

 

BOOL CParseSql::IsType(CApiEdit &ae,int s, int e)

{

     static TCHAR *type="bigint binary bit char datetime "

          "decimal float image int nchar ntext numeric "

          "nvarchar money real smalldatetime smallint smallmoney "

          "sql_variant text timestamp tinyint uniqueidentifier "

          "varbinary varchar ";

 

     return IsStringExist(type,ae.buf+s,e-s+1,FALSE);

}