JParser (11) 결론 1 실제 사용 사례
jparser 사용 사례
내가 개인 프로젝트에서 jparser를 사용한 사례들을 모아보았다.
CDG meta language
일반적으로 새로운 언어를 만들 때 그 언어의 표현력이 충분하다는 것을 증명하기 위한 첫 단계가 그 언어로 해당 언어의 컴파일러나 인터프리터를 만드는 것이다. 즉, 코틀린이라는 언어를 만들 때 코틀린이란 언어가 쓸만 하다는 것을 증명하기 위해서 코틀린 컴파일러를 코틀린으로 만드는 것이다. 물론 코틀린 컴파일러를 처음 만들 때는 코틀린 컴파일러가 없는 상태이니 다른 언어를 사용해야겠지만, 구현체가 한번 만들어지면 그때 컴파일러 전체를 코틀린으로 재구현할 수 있을 것이다.
비슷한 의미로 CDG의 문법은 CDG로 정의되었다. (문법 정의) 나 스스로가 이렇게 자동화된 파서 제너레이터를 쓰면 문법을 수정하는 것이 얼마나 간편한지 처음으로 체감할 수 있었다.
JSON 파싱
JSON은 의외로 평이한 CDG만으로도 정의가 가능하고 문법도 상당히 간단하다. 문법 정의
bibix
bibix는 빌드 툴이다. 빌드 툴을 왜 또 만들었을까.. 라고 의아하게 생각할 수 있다. 나도 충분히 이해한다. 하지만 내 생각에는 필요성이 있다고 생각해서 만들었고, 내가 보기엔 결과물이 나쁘지 않다. 나는 자바, 스칼라, 코틀린같은 JVM 기반 언어를 즐겨 사용하기 때문에 maven, gradle, sbt 등등을 사용해 보았는데, 이들보다 분명히 나은 점이 있다고 생각한다. 물론 이런 툴들의 에코 시스템이나 오랜 역사, 사용자 기반을 무시할 수는 없지만 툴만 놓고 본다면 bibix의 장점이 있다는 것이다. 이 툴에 대한 이야기는 별도의 포스팅 시리즈에서 다루기로 하자.
여하튼 bibix는 자체적인 스크립팅 언어를 갖고 있고, 이 언어를 CDG로 정의하고, jparser로 파서를 생성해서 실제 bibix 구현에서 생성된 파서를 사용하였다. (문법 정의)
ASDL
CDG만으로는 파이썬을 파싱할 수 없다. 방법이 아주 없지는 않을 것 같지만 적어도 현재로써는 쉽지 않다. 들여쓰기 수준을 이용한 블록 문법이 구현하기에 만만치 않다. CDG를 최대한 활용해서 파이썬 파서를 만드는건 중요한 future work이지만, 그 전에 나는 당장 파이썬 분석기를 하나 만들고 싶었다.
파이썬에는 ast라는 모듈이 있어서 파이썬 프로그램을 스트링으로 주면 그 파싱 결과로써의 ast를 반환해준다. 당장 만들고 싶었던 파이썬 분석기에서 이 기능을 활용하고 싶었다. 파이썬에서 ast.parse를 실행하고, 그 결과를 protobuf 메시지로 변환하고, 변환된 메시지를 코틀린 클래스들로 변환해서 코틀린에서 쓰고 싶었다. 물론 분석기를 파이썬으로 만들면 그런 복잡한 과정을 거치지 않아도 되겠지만 나는 파이썬으로 복잡한 프로그램을 짜는 것은 피해야 한다고 믿는 사람이라 그건 선택지가 아니었다.
그러려면 ast.parse의 결과로 나오는 데이터를 나타낼 수 있는 protobuf 스키마 정의, 파이썬으로 작성된 ast→protobuf 변환 코드, 코틀린의 클래스 정의, 코틀린에서 protobuf→코틀린 클래스로 변환하는 코드까지 총 4가지 코드가 필요했다. 코틀린에서 protobuf 클래스를 그냥 사용할 수도 있지만, oneof 필드들을 sealed class로 변환해서 쓰면 코드가 좀더 간결해지기 때문에 protobuf 클래스를 그냥 쓰기보단 한번 변환해서 쓰고 싶었다.
다행히도 파이썬 ast 모듈 문서에서 ast.parse가 반환하는 클래스 구조를 ASDL이라는 상당히 간단한 문법으로 정의하고 있었다. 그래서 ASDL 문법을 CDG로 정의해서 ASDL 파서를 만들고, 위에서 이야기한 네가지 코드를 생성하는 프로그램을 만들었다. ASDL 문법은 상당히 단순해서 다음과 같이 20줄도 되지 않는 짧은 CDG 정의로 나타낼 수 있다. 아마 원래도 lex/yacc으로 파서를 만든 것 같다. (문법 정의)
Defs = WS ModuleDef WS $1
ModuleDef = "module"&Tk WS Name WS '{' WS SuperClassDef (WS SuperClassDef)* WS '}' {ModuleDef(name=$2, defs=[$6] + $7)}
SuperClassDef = Name WS '=' WS SuperClassDefBody (WS AttributesDef)?
{SuperClassDef(name=$0, body=$4, attrs=$5)}
SuperClassDefBody: SuperClassDefBody = SubClassDef (WS '|' WS SubClassDef)* {SealedClassDefs(subs=[$0] + $1)}
| Params {TupleDef(body=$0)}
AttributesDef = "attributes"&Tk WS Params {Attributes(attrs=$2)}
SubClassDef = Name (WS Params)? {SubClassDef(name=$0, params=$1)}
Params = '(' WS Param (WS ',' WS Param)* WS ')' {[$2] + $3}
Param = Name (WS ('*' { %REPEATED } | '?' { %OPTIONAL }))? WS Name
{Param(typeName=$0, typeAttr: %TypeAttr = $1 ?: %PLAIN, name=$3)}
Name = <'a-zA-Z_'+ {str($0)}>
Tk = <'a-zA-Z_'+>
WS = (' \t' | NEWLINE | LineComment)*
NEWLINE = <"\n" | "\r" | "\r\n">
LineComment = "--" (. !NEWLINE)* .
이렇게 해서 만들고 있는게 파이썬 분석기인 tython이다. 이 프로젝트는 추후에 다른 포스팅에서 좀더 이야기하기로 하자.
(계획) 자바 컴파일러 튜토리얼
자바(비슷한 언어)를 LLVM으로 컴파일하는 튜토리얼을 작성해보려고 한다. 이 계획도 벌써 1년 이상 묵혀두고 있는 계획이라 언제 실현될지는 알 수 없긴 하다. 2년 전에 자바 문법의 CDG 버젼을 만들어두었다. (문법이 정확한지 검증은 더 필요할 것 같다)
이 튜토리얼에서 자바의 모든 기능을 지원할 수는 없을 것이고, 자바 언어의 subset을 정의하고 LLVM으로 컴파일하는 프로그램을 만들고, 그 과정을 튜토리얼로 적어보려고 한다. 어느정도 만든 다음에는 자바에 없어서 아쉬운 스트링 템플릿, 타입 인퍼런스같은 기능이나 코틀린의 data class, 코루틴같은 기능을 추가해보면 어떨까 싶다. (최근에는 자바에도 타입 인퍼런스를 지원하는 var 키워드, data class와 비슷한 record 등이 들어가서 약간 김이 샌 느낌은 있지만) 나도 이렇게 제대로 된 컴파일러를 만들어 본 적은 없어서 예상치 못했던 어려운 점들도 많이 있을테니 공부도 되고 재미있지 않을까 싶다. 문법도 고쳐보고 새로운 기능 구현도 해보고.
(계획) 자바스크립트 파싱
앞의 소회에서 밝힌대로 jparser는 원래 자바스크립트를 파싱하기 위한 파서를 만들기 위해 시작한 프로젝트였다. 그래서 jparser 프로젝트 초창기부터 자바스크립트는 중요한 대상 언어였다. 그래서 지금 정립된 CDG 문법 정의 언어가 만들어지기도 전부터 자바스크립트 언어 정의를 만들어서 테스트하기도 했었다. 그리고 지금 CDG에 포함된 확장 심볼들을 설계하는 데 있어서도 자바스크립트는 중요한 기준이었다.
그래서 이번에 이 포스팅 시리즈를 쓰면서 자바스크립트 파싱을 제대로 해보려고 ECMA-262 문서를 열어봤다 놀랐다. jparser를 처음 시작했을 때는 자바스크립트 문법이 지금보다 훨씬 단순했던 것으로 기억하는데, 지금은 문법이 엄청나게 복잡하고 비대해져 있었기 때문이다. 무엇보다 Parameterized production이니 parameterized nonterminal이니 하는 것들이 생겨서 아주 귀찮다. 이것들 때문에 CDG로 가기 전에 전처리기를 하나 만들어야 할 것 같은데, 자바스크립트 파싱이 별로 필요가 없어서 이 역시 언제가 될 지는 기약이 없다.
전체 목차:
다음 포스팅에서는 jparser와 관련해 아직 남은 할 일들을 정리해본다.
- JParser (1) 파싱
- JParser (2) Conditional Derivation Grammar
- JParser (3) CDG 사용 팁
- JParser (4) Naive 파싱 알고리즘
- JParser (5) Naive 파싱 알고리즘 동작 예제
- JParser (6) 승인 조건
- JParser (7) 파스 트리 재구성
- JParser (8) 마일스톤 파싱 알고리즘 (WIP)
- JParser (9) Abstract Syntax Tree 프로세서
- JParser (10) AST 프로세서 구현 (WIP)
- JParser (11) 결론 1 실제 사용 사례
- JParser (12) 결론 2 future works