.분석기 관리

분석기 객체는 이미 다 만들었고 다음은 ApiEdit와 분석기 객체를 연결시켜야 한다. 구문 분석은 분석기가 하고 ApiEdit는 그 결과를 사용하여 화면에 출력할 것이다. ApiEdit.h에 다음과 같이 분석기 객체의 포인터를 선언한다.

 

class CApiEdit

{

     friend class CParse;

     friend class CParseCpp;

     friend class CParseNull;

     friend class CParseHtml;

     friend class CParseSql;

private:

     ....

     CParse *Parser;

public:

     ....

     CParse* GetParser() { return Parser; }

     void SetParser(int ParseType);

 

CParse형의 포인터 변수 Parser를 멤버로 포함시킨다. 실행중에 분석기를 수시로 교체할 수 있어야 하므로 분석기 객체 자체를 멤버로 가질 수 없으며 반드시 포인터형 변수를 가져야 한다. Parser 멤버는 분석기 루트 클래스인 CParse형의 포인터이므로 CParseNull을 가리킬 수도 있고 CParseCpp를 가리킬 수도 있으며 이후 만들어지는 CParse의 모든 파생 클래스를 포인트할 수 있다. 초기값은 NULL이어야 하므로 생성자에서 Parser NULL로 초기화한다.

 

CApiEdit::CApiEdit()

{

     ....

     Parser=NULL;

}

 

분석기는 구문 분석을 위해 ApiEdit buf, off, pLine 등의 핵심 멤버를 자유롭게 액세스해야 한다. 이 멤버를 public으로 공개하거나 읽을 수 있는 멤버함수를 추가할 수도 있지만 이렇게 되면 외부에서도 마음대로 액세스가 가능해지므로 바람직하지 않다. 분석기 객체만 ApiEdit를 액세스할 수 있도록 하기 위해 CParse, CParseCpp, CParseNull 클래스를 프렌드로 선언하였다. 이렇게 되면 분석기의 멤버함수는 ApiEdit의 모든 멤버에 대해 자유롭게 액세스할 수 있다. 잠시 후에 만들기 될 HTML 분석기인 CParseHtml SQL 분석기인 CParseSql도 미리 프렌드로 선언해두었다.

GetParser 함수는 현재 선택된 분석기의 포인터를 리턴한다. 호스트가 분석기와 직접 통신하기 위해서는 분석기 객체의 포인터를 얻을 수 있어야 한다. SetParser 함수는 분석기의 ID값을 인수로 전달하면 해당 ID의 분석기를 선택한다. 분석기는 모두 클래스로 정의되어 있으므로 new, delete 연산자로 편리하게 삭제, 생성할 수 있다.

 

void CApiEdit::SetParser(int ParseType)

{

     if (Parser != NULL) {

          delete Parser;

     }

 

     switch(ParseType) {

     case -1:

          break;

     case 0:

          Parser=new CParseNull;

          break;

     case 1:

          Parser=new CParseCpp;

          break;

     case 2:

          Parser=new CParseHtml;

          break;

     case 3:

          Parser=new CParseSql;

          break;

     }

 

     Invalidate(-1);

}

 

이전에 사용하던 분석기가 있다면 delete 연산자로 삭제하는데 이때 분석기의 파괴자가 호출되며 사용하던 모든 메모리를 반납할 것이다. 이전 분석기를 제거한 후 인수로 전달된 ParseType에 따라 새 분석기를 new 연산자로 생성한다. 0번이 기본 분석기, 1번이 C 언어 분석기로 정의되어 있으며 이후 추가되는 분석기는 계속 해서 2, 3번의 ID를 할당하면 된다. 특별한 경우로 -1은 분석기가 없는 상태를 만드는데 실행중에는 이런 상태가 될 수 없으며 종료하기 직전에 분석기 제거를 위해 사용된다.

분석기가 교체되면 새 문법에 따라 구문 분석을 다시 하게 될 것이고 화면도 다시 출력되어야 하므로 전체 화면을 무효화하여 다시 그리도록 한다. 그리는 과정에서 새 분석기가 동작하도록 되어 있으므로 변경된 분석기의 분석결과가 즉시 화면에 나타난다. 분석기 연결을 위해 ApiEdit에 추가되어야 하는 코드는 이 정도뿐이다. 대부분의 분석 관련 코드가 분석기 클래스에 작성되어 있기 때문이다. ApiEdit의 나머지 관련 코드는 조금씩 수정하기만 하면 된다.

먼저 SetDefaultSetting에서 초기 사용할 분석기를 선택한다. 원칙대로라면 아무런 분석도 하지 않는 기본 분석기를 선택하는 것이 옳겠지만 지금은 개발중이므로 제대로 동작하는지 살펴보기 위해 C 언어 분석기를 선택하도록 했다. 현재 동작하는 분석기가 C 언어 분석기밖에 없으니 1번 분석기로만 테스트를 해 볼 수 있기 때문이다.

 

void CApiEdit::SetDefaultSetting()

{

     ....

    SetParser(1);

}

 

SetParser에서 CParseCpp 객체를 생성할 것이며 이 객체의 생성자와 InitInfo에서 분석 정보 저장을 위한 pInfo 배열을 할당할 것이다. 문서가 변경될 때마다 분석 정보는 다시 작성되어야 하므로 InitDoc에서도 분석기의 InitInfo를 호출해야 한다.

 

void CApiEdit::InitDoc()

{

     int i;

 

    Parser->InitInfo(TRUE);

     if (pUR) {

     ....

 

UpdateLineInfo 함수에서 재분석을 위해 분석기의 멤버함수를 호출하므로 가급적이면 InitDoc의 선두에 이 코드를 작성해야 한다. 이 호출에 의해 모든 분석 정보는 삭제되고 pInfo는 다시 할당될 것이다. OnDestroy 에서는 분석기를 제거한다.

 

void CApiEdit::OnDestroy(HWND hWnd)

{

     ....

 

    SetParser(-1);

}

 

분석기가 추가됨으로써 ApiEdit는 구분자를 직접 정의할 필요가 없어졌다. 문법강조 기능이 없을 때는 모든 문서가 동일한 문법의 적용을 받으므로 ApiEdit가 구분자를 정의했지만 이제 문서의 문법에 따라 구분자가 달라지므로 구분자의 목록을 분석기로부터 제공받아야 한다. 분석기의 구분자 목록은 GetInfo(1) 함수 호출로 조사할 수 있다. IsDelimiter 함수의 구분자 목록은 제거하고 분석기로부터 구분자 목록을 전달받도록 수정한다.

 

BOOL CApiEdit::IsDelimiter(int nPos)

{

     return (strchr(Parser->GetInfo(1),buf[nPos]) || buf[nPos]==0);

}

 

nPos 위치의 문자가 Parser가 정의하는 구분자 목록에 있는 문자인지 조사한다. 분석기가 바뀌면 구분자의 목록도 바뀌게 되므로 단어 단위 이동이나 단어 선택 기능들이 자연스럽게 사용 문법의 적용을 받게 된다. 특수한 경우로 문서의 끝인 경우는 문법에 상관없이 무조건 구분자로 인정한다.