Tag Archives: dsl

고객과 함께 하는 도메인 특화 언어 DSL

4년 전쯤에 번역을 한 적이 있다. 아는 선배의 소개로 시작했고, 1년 정도면 충분하리라고 믿었다. 결국 2년이 걸렸고, 마치 숙제검사를 기다리는 학생처럼 스트레스도 이만저만이 아니었다. 그래도 끝나고 나니 후련하기는 했다. 다만 생각만큼 책이 많이 팔리지 않은 듯하여 인사이트 사장님께는 늘 죄송한 마음이다.

올해에는 번역이 아니라Elasticssearch 관련하여 책을 한권 써볼 요량으로 도전했다가 중도포기했다. 서점에 나와 있는 저 많은 IT 서적들을 보면서, 바쁜 시간을 쪼개 글을 써 주지는 저자들이 존경스럽다.

 

드디어 나왔습니다.

“고객과 함께 하는 도메인 특화 언어 DSL”

dsl_title

번역을 부탁 받고, 처음으로 원서를 접했을 때의 느낌은 두려움이었습니다. 두께도 두껍거니와, 내용 자체도 상당히 어려웠기 때문입니다. 지금까지는 접하지 못했던 새로운 내용들과, 현장에서는 거의 쓰이지 못하던 기술들 때문에 번역은 커녕 내용을 이해하는데도 거의 3달이 넘게 걸렸습니다.

그렇게 책을 다 읽은 후, 초벌 번역을 진행하면서 두번째로 읽었을 때의 느낌은 놀라움이었습니다.

아! 이런 일도 가능하구나!

이렇게 쉬운 방법이 있구나!

한 장, 한장을 넘기면서 새로운 패러다임에 눈을 뜨기 시작했습니다.

초벌 번역을 다듬으면서 세번째로 읽었을 때의 느낌은 막막함이었습니다. 내용은 신선하지만, 과연 어떤 분야에 적용할 수 있을지, DSL을 적용하기에는 한글과 영어의 격차가 너무 큰 건 아닌지, 과연 현실성이 있는지 조금도 확신할 수 없었습니다.

퇴고를 하면서 네번쨰로 읽었을 때의 느낌은 즐거움이었습니다. 웹로그를 파싱하면서, ANTLR 파서 생성기를 실제로 적용해보면서
이전에는 시도하지도 못했던 일들을 해낼 수 있었습니다. 이를 통해 프로그래밍의 즐거움을 새삼 느낄 수 있었습니다.

책을 접하는 독자분들도 저와 같은 감정 기복을 분명히 느낄 것입니다. 또한 저와 같은 시행 착오를 이겨낸다면, 이 책은 독자 여러분에게 프로그래밍에 대한 새로운 시각을 열어줄 것입니다.

 

ANTLR을 사용한 웹로그 파서 만들기

컨텍스트

웹로그의 경우 아래와 같이 그 형식이 다양하다.

문자열을 파싱할 때 대개의 경우 구분자 주도 변환 기법을 사용한다. 즉, 파싱하려는 문자열에서 사용하지 않는 특수한 문자를 구분자로 선택한 후, 그 구분자를 사용해 문자열을 나누는 방식이다. 위와 같은 로그라면 공백을 사용해 문자열을 분할할 수 있지 않을까라는 생각이 먼저 떠오른다.

하지만 생각과는 달리, 구분자 주도 변환 기법은 이 경우에 그리 효과가 없다. sun one의 로그를 보면 알겠지만 URL 부분의 경우 쌍따옴표로 묶인 부분을 하나의 토큰으로 분할해야하지만, 쌍따옴표 안에 공백이 이미 사용되고 있기 때문이다.

구분자 대신 사용할 수 있는 방법으로 정규 표현식을 고려해볼 수 있다. 하지만 정규 표현식을 사용하게 되면, 정규 표현식 자체가 복잡할뿐만 아니라, 정규식으로 추출한 토큰은 그 순서에 따라 의미가 달라지게된다. 예를 들어 iis의 경우에는 두 번째 토큰이 IP인데 반해, sun one의 경우에는 URL과 관련된 정보다.

결국 웹로그 파서를 만드는 경우, 구분자 주도 변환 기법이나 정규식을 이용해 파서를 직접 만드는 방식 모두 효과가 없다.

해결책

문법을 작성하고, 문법을 기반으로 파서를 생성하는 구문 주도 변환 기법을 사용하고자 한다. 파서 생성기로는 널리 알려진 ANTLR을 사용한다. 그리고 파서의 결과로는 트리를 생성한다. 파서에서 시맨틱 모델을 바로 생성할 수도 있지만, 추상 구문 트리(AST)를 만든 후, 트리를 다시 시맨틱 모델로 변환하는 방식이 더 효과적이다. 웹로그를 파싱하는 경우 시맨틱 모델은 단순히 웹로그 도메인 객체다.

Step By Step

설치하기

ANTRL 사이트에서 ANTLRWorks를 다운로드한다. 현재 최신 버전은 1.4.3이다.

Window의 경우, 다운로드 받은 디렉토리로 이동해서 아래의 명령어를 실행한다.

Window 이외의 플랫폼을 사용중이라면, How to run ANTLRWorks를 참고한다.

문법 만들기

아래와 같이 문법 파일을 작성한다. ANTLR에서는 렉싱 규칙과, 파싱 규칙을 하나의 문법에서 작성한다.

위와 같이 문법파일을 작성한 후, Weblog.g라는 이름으로 저장한다. 이때 최상위의 문법명(Weblog)와 파일명은 같아야 한다.

렉싱 문법 테스트하기

작성한 렉싱 문법을 테스트한다. 하단의 Interpreter탭을 연다. 왼쪽의 텍스트 영역에 테스트할 입력 텍스트를 붙여넣는다. 여기에서는 아래의 웹로그를 테스트한다.

그리고나서, 명령버튼 영역에서 실행버튼을 클릭하면 아래와 같은 파스 트리를 볼 수 있다.

antlr_parse_tree

그림. ANTR 파스 트리

트리 생성 구문 추가하기

렉싱 규칙에 대한 테스트가 끝나면, 이제 트리를 생성하는 구문을 문법에 추가한다. ANTLR에서는 트리를 쉽게 생성할 수 있는 특수한 구문을 제공한다. 아래와 같이 문법을 수정한다.

렉서 코드와 파서 코드 생성하기

문법파일 작성이 완료되면, 아래의 명령어를 실행해서 렉서와 파서를 생성한다.

코드가 성공적으로 생성되면, 하위 폴더에 Weblog.tokens, WeblogLexer.java, WeblogParser.javar, 이렇게 세 파일이 생성된다.

또는 Generate 메뉴에서 [Generate Code] 메뉴를 선택한다. 이경우에는 문법파일이 위치한 디렉토리의 하위 디렉토리인 output에 생성된다. 디버깅 툴에서는 생성된 코드가 output 디렉토리에 있다고 가정하므로, 메뉴를 통해서 코드를 생성할 것을 추천한다.

파싱 규칙 테스트하기

트리 생성 구문까지 작성이 완료되면, 파싱 규칙을 테스트한다. 문법 파일을 수정했으므로, 코드를 새로 생성한다. 코드 생성이 끝나면, Run 메뉴에서 [Debug…] 메뉴를 선택한다.

antlr_debug_parse_tree

텍스트 영역에, 테스트하려는 로그 텍스트를 붙여넣은 후, OK 버튼을 누른다. 그리고 나서 [Go to End] 버튼을 클릭하면, 아래와 같이 파스 트리가 간소화시킨 AST가 정상적으로 만들어짐을 확인할 수 있다. (때에 따라 원격 서버에 접속할 수 없다는 메시지가 뜨는 경우가 있는데, 이 경우에는 ANTLRWorks를 재기동한다).

antlr_parse_tree_ast

생성할 코드에 속성 부여하기

아무런 속성을 지정하지 않을 경우, 렉서와 파서는 디폴트 패키지에 생성된다. 따라서 패키지를 설정하거나, 옵션을 주어 생성할 소스코드의 속성을 수정한다.

문법이 변경되었으므로, 코드를 다시 생성한다.

시맨틱 모델 클래스 만들기

여기에서는 단순히 웹로그를 저장할 도메인 클래스 하나면 충분하다. 여기에서는 별도의 도메인 클래스를 만들지 않고 HashMap<String, String>에 저장한다.

로더 프로그램 만들기

이제 렉서와 파서를 실행할 로더 프로그램을 만든다. 먼저 아래와 같이 이클립스에서 자바 프로젝트를 하나 생성한다. 그리고 다운로드받은 antlrworks-1.4.3.jar 파일을 라이브러리에 추가한다. 여기에서는 메이븐 프로젝트를 만들었고, 의존성 라이브러리에는 antlr-runtime과 junit4를 추가했다.

antlr_loader_package_structure

로더 클래스를 만들기 전, 테스트 케이스를 작성한다. 테스트케이스는 단순하다. 테스트를 실행하기 전, 로그데이터를 담고있는 파일을 읽어서 Reader 객체에 저장한다.

Reader 객체를 로더 객체를 생성하면서 생성자의 인자로 전달한다.

로더 클래스에서 가장 핵심이 되는 메서드는 run()으로, AST를 로드한 후 시맨틱 모델을 만드는 역할을 한다.

로그데이터를 담고 있는 샘플 파일을 만든다.

테스트케이스를 실행하면 성공했음을 알 수 있다.

추가할 사항

AST에서 자식 노드를 가져올 때 그 순서는 웹로그에 따라 다르다. 따라서 가져온 자식 노드가 어떤 데이터인지 알려면, 각 웹로그 유형별로 로그의 타입정보를 저장해야 한다. 그리고 나서 런타임에 로그 타입 정보를 읽어서, 몇 번째 노드가 무슨 데이터인지 인식하도록 프로그램을 개선해야 한다.

참고자료