Lexilla icon Scintilla

렉실라와 함께 Scintilla 5로 어플리케이션 이주하는 법.

들어가는 말

신틸라 5.0부터 모든 렉서는 Lexilla 라이브러리에 통합되어 별도의 프로젝트로 관리된다. 렉실라는 정적 라이브러리로서 어플리케이션 안으로 링크되거나 공유 라이브러리로서 실행시간에 적재된다.

렉실라는 따로 문서가 있다.

기초 정보

신틸라 4.x에서는 일반적으로 SCI_SETLEXERLANGUAGE("<name>") 또는 SCI_SETLEXER(SCLEX_*)를 호출함으로써 어플리케이션이 렉서 이름 (SCLEX_...)이나 렉서 ID로 렉실라를 설정했다.

신틸라 5부터는 보통 렉실라의 CreateLexer 함수를 렉서 이름으로 호출한 다음, 그 결과를 신틸라의 SCI_SETILEXER 메쏘드에 적용한다:
ILexer5 *pLexer = CreateLexer("<name>")
SCI_SETILEXER(pLexer)

이제는 렉서 이름이 렉서 ID보다 더 좋다. 어플리케이션은 되도록이면 렉서 이름을 사용해야 한다. ID가 없는 렉서는 있을 수 있지만 모든 렉서는 이름이 있기 때문이다.

어플리케이션은 C++ 또는 C로 작성할 수 있으며; 정적으로 링크된 렉실라 라이브러리를 포함하거나 렉실라 공유 라이브러리를 적재할 수 있고; 렉실라 API 또는 LexillaAccess C++ 도움자 모듈을 사용할 수 있다.

C++로 구현하는 법: LexillaAccess 이용

가장 쉬운 방법은 C++로 구현하는 것이다. LexillaAccess를 사용하고 렉실라 공유 라이브리를 적재하면 된다.

LexillaAccess는 렉실라를 쉽게 사용할 수 있도록 해주며, 운영체제 사이의 차이를 감추어 주고, 렉실라 프로토콜만 지원한다면 여러 라이브러리를 적재할 수 있도록 해준다. lexilla/access/LexillaAccess.h에 정의되어 있으며 소스 코드는 lexilla/access/LexillaAccess.cxx에 있다. 이 파일들을 프로젝트 또는 빌드 파일에 추가하자.

SciTE와 TestLexers (lexilla/test에 있고 렉실라 테스트에 사용됨) 모두 LexillaAccess를 사용한다. TestLexers가 SciTE보다 훨씬 더 단순하므로 연구하는 데 더 좋을 수 있다.

빌드하기

렉실라를 사용하는데 필요한 헤더 파일들과 렉서용 헤더 파일들이 포함되어야 하므로 빌드 파일은 새 위치를 참조할 필요가 있다. LexillaAccess.cxx는 소스 파일에 추가해야 한다. 예를 들어 make를 사용한다면 다음과 같은 모습이 될 것이다:

LEXILLA_DIR ?= $(srcdir)/../../lexilla
INCLUDES += -I $(LEXILLA_DIR)/include -I $(LEXILLA_DIR)/access
SOURCES += $(LEXILLA_DIR)/access/LexillaAccess.cxx

코딩 순서

경로가 주어지지 않으면 렉실라를 검색할 디렉토리를 설정한다. 이 디렉토리는 잘 알려진 시스템 디렉토리나 사용자 디렉토리 아니면 어플케이션 디렉토리일 수 있다. 이는 운영체제의 관례에 달려 있다.

std::string homeDirectory = "/Users/Me/bin";
Lexilla::SetDefaultDirectory(homeDirectory);

렉실라를 적재하거나 아니면 렉실라 프로토콜을 구현한 다른 라이브러리를 적재한다. "." 이름은 표준 이름과 확장자를 의미한다 (liblexilla.so / liblexilla.dylib / lexilla.dll).기본 디렉토리에 있으므로 좋은 선택이 될 수 있다. 이름에 확장자가 없다면 운영체제가 선호하는 공유 라이브러리 확장자가 덧붙는다. 완전한 경로를 사용할 수도 있다. 여러 라이브러리를 한꺼번에 적재하려면 ';'로 갈라서 나열해 준다. 다음과 같이 말이다:

Lexilla::Load(".");
Lexilla::Load("lexilla;lexpeg;XMLexers");
Lexilla::Load("/usr/lib/liblexilla.so;/home/aardvark/bin/libpeg.so");

사용할 렉서의 이름을 고른다. 파일 확장자나 또는 기타 메커니즘으로 결정할 수 있다.

std::string lexerName = "cpp";

렉서를 생성하고 그것을 신틸라 실체에 적용한다.

Scintilla::ILexer5 *pLexer = Lexilla::MakeLexer(lexerName);
CallScintilla(scintilla, SCI_SETILEXER, 0, pLexer);

어플리케이션은 SciLexer.h에 있는 정수형 렉서 ID를 사용해도 된다. SCLEX_CPP와 같이 앞에 "SCLEX_"가 붙어 있다. NameFromID를 사용하여 이름으로 변환한 다음 MakeLexer에 건넬 수 있다.

Scintilla::ILexer5 *pLexer = Lexilla::MakeLexer(Lexilla::NameFromID(SCLEX_CPP));

C로 구현하는 법: Lexilla.h 그리고 시스템 API 이용

C나 C++로 작성된 어플리케이션은 렉실라 API를 그대로 사용할 수 있다. lexilla/include/Lexilla.h에 정의되어 있다. ILexer 인터페이스는 C에 정의하기가 매우 어렵기 때문에, C 어플리케이션은 CreateLexer의 결과를 void*로 간주하고 이것이 그냥 신틸라에 건네진다. 렉서에 메쏘드를 호출한 다음에 그 결과를 신틸라에 건넬 필요가 있다면, 가장 좋은 방법은 C++ 코드를 좀 사용하는 것이다. 그렇지만 충분한 이유가 있다면 개밸자는 C 언어로 렉서에 메소드를 호출할 수 있다.

Lexilla를 C로 사용하는 예제는 CheckLexilla에 있다.

코딩 순서

공유 객체들을 적재하는데 필요한 시스템 헤더들을 인클루드한다. 시스템 헤더는 운영제에 따라 다르다: <windows.h>는 Windows 또는 <dlfcn.h>는 Unix에 해당된다.

#include <windows.h>
#include <dlfcn.h>

Lexilla 공유 라이브러리 경로를 지정한다. 시스템 디렉토리나 사용자 디렉토리 또는 어플리케이션 디렉토리일 수 있다. 운영체제의 관례에 따른다.

#include "Lexilla.h"
char szLexillaPath[] = "../../bin/" LEXILLA_LIB LEXILLA_EXTENSION;

운영체제의 적절한 함수를 사용하여 렉실라를 적재한다: 윈도우즈라면 LoadLibrary이고 Unix라면 dlopen이 될 것이다.

HMODULE lexillaLibrary = LoadLibrary(szLexillaPath);
void *lexillaLibrary = dlopen(szLexillaPath, RTLD_LAZY);

공유 라이브러리 안의 CreateLexer 함수를 찾는다. 윈도우즈라면 GetProcAddress이거나 Unix라면 dlsym이 될 것이다.

FARPROC fun = GetProcAddress(lexillaLibrary, LEXILLA_CREATELEXER);
void *fun = dlsym(lexillaLibrary, LEXILLA_CREATELEXER);

이것을 적절하게 형변환한다.

CreateLexerFn lexerCreate = (CreateLexerFn)(fun);

사용할 렉서의 이름을 고른다. 이름은 파일 확장자나 기타 메커니즘으로 결정된다.

char lexerName[] = "cpp";

렉서를 생성해서 그것을 신틸라 실체에 적용한다.

void *pLexer = lexerCreate(lexerName);
CallScintilla(scintilla, SCI_SETILEXER, 0, pLexer);

어플리케이션이 정수형 렉서ID를 사용하면 LEXILLA_LEXERNAMEFROMID함수를 찾아서, LexerNameFromIDFn로 형변환한 다음에 그 ID로 호출하자. 그 결과를 가지고 CreateLexer를 호출하면 된다.

FARPROC funName = GetProcAddress(lexillaLibrary, LEXILLA_LEXERNAMEFROMID);
void *funName = dlsym(lexillaLibrary, LEXILLA_LEXERNAMEFROMID);
LexerNameFromIDFn lexerNameFromID = (LexerNameFromIDFn)(funName);
const char *name = lexerNameFromID(lexerID);

정적으로 링크하기

렉실라를 직접 어플리케이션에 링크하거나 정적 라이브러리에 링크한 다음 어플리케이션에 링크할 수 있다. 그러면 공유 라이브러리를 적재할 필요가 없고 CreateLexer를 직접적으로 호출할 수 있다.

렉실라를 어플리케이션에 링크한 다음, 렉실라 프로코콜을 지원하는 다른 공유 라이브러리를 동적으로 적재할 수 있다. 윈도우즈 SciTE는 이것을 옵션으로 구현했다. 빌드 시 STATIC_BUILD를 정의하면 된다.