[Nox] 스크립트 언어 설계 및 컴파일러 만들기 + α (3)

스크립트 언어도 만들었고 그 언어로 작성된 소스코드를 파싱하고 컴파일도 할 수 있게 되었으니, 이제 개발 환경을 만들 차례다! 요즘에는 sublime이나 atom 같은 확장 가능한 에디터가 꽤 많기 때문에 어떤 것으로 고를지 결정하는 것도 고민이었다. 그러던 중 얼핏 Visual Studio Code (vscode)에서 language server라는걸 사용하면 에디터에 다소 어렵지 않게 새로운 언어의 지원을 추가 가능하다고 본 기억이 나서 vscode로 결정했다. 하지만, 작업하다 보니 생각보다 새로운(?) 기능이라 그런지 예제나 documentation을 찾을 수 없어서 꽤 고생했다 :(

home-screenshot-win-lg

Visual Studio Code

일단 인터페이스 깔끔하고, 쉬운 extension 관리와 Linux/Windows/macOS에서 모두 돌아가는 cross-platform. 그리고 오픈소스라는 점이 장점이다. 디버깅과 git 서포트 등의 기능도 지원하지만 우리는 딱히 필요 없다. vscode의 익스텐션은 보통 javscript나 typescript로 작성된다.

VSCode Language Extension

Language server는 에디터 프로세스에서 처리하기에 오래 걸리거나 외부 프로그램 등을 참조해야할 때 유용하다. 프로토콜에 대한 설명은 github을 통해 볼 수 있다. 사실 우리는 그렇게까지 복잡한 작업을 하는게 아니므로 굳이 이 방법을 안써도 되지만, 이 패러다임(?)이 MS에서 앞으로 추천하는 방법인것 같으니 시도해보도록 한다. 아주 간단한 boilerplate 프로젝트가 있으니 clone해서 사용하면 편하다.

Language server를 사용한 vscode의 언어 지원 익스텐션은 크게 client와 server, 두 component로 나뉜다. Client에서는 익스텐션의 커스텀 커맨드나 설정 관리, syntax highlighting 등을 담당하고, server에서는 IntellisSense를 위한 작업을 담당한다. 또한,  Client와 server 간에 통신을 하며 클라이언트에서 특정 이벤트 발생 시에 서버에 전달하고 결과를 받아 처리한다.

Client

클라이언트에서는 두 가지를 처리한다: Syntax highlighting, Compile 커맨드.

우선, extension을 패키징 할 때 쓰이는 설정 파일인 package.json을 알맞게 수정해준다. 여기서 중요한 것은 activationEvents에서 onLanguage:ns를 통해 ns 언어로 된 파일을 열었을 경우에 익스텐션이 활성화 되도록 설정하고, 그 아래 contributes 옵션에 languages에 이 익스텐션이 이해하는 언어를 추가해준다 — 여기서 id는 이전의 onLanguage에 사용된것과 일치해야하고, extensions 옵션을 통해 특정 확장자 파일을 열 경우 이 언어로 이해하라고 설정 가능하다.

이 설정 파일에서 참조되는 language-configuration.json은 에디터에서 코드 접기 기능이나 자동 따옴표/괄호 완성 (여는 괄호를 쳤을때 닫는 괄호도 자동으로 만들어주는 것)을 위한 설정이다. 이건 뭐 기존 언어들과 크게 다를게 없으므로 C 언어용을 그대로 가져다 쓴다.

Syntax Highlighting

신택스 하이라이팅 지원은 따로 코딩할 필요가 없다. syntaxes 폴더 안에 noxscript.tmLanguage와 같은 textmate language 파일을 넣어주고 package.json에 위와 같이 grammar를 추가해주면 된다.

tmLanguage 파일은 plist 형식으로 작성된 언어의 문법 매칭 룰 (match)과 그에 해당하는 이름 (name)을 정의한다. Sublime, TextMate, VSCode 모두 이렇게 정의한 syntax 룰을 통해 highlighting 할 수 있다. 우리는 C-like 언어이므로 C를 위해 작성된 tmLanguage 파일을 조금 변형하여 사용한다 (keyword 및 built-in 함수명들 추가 / 필요 없는 매칭 룰들 삭제): noxscript.tmLanguage

syntax_highlight

Compile Command

에디터에서 스크립팅을 하고 저장된 파일을 가지고 이전에 만든 컴파일러를 손수 돌려서 컴파일 해도 되지만, 에디터 내에서 바로 컴파일러를 돌려서 결과를 볼 수 있다면 편하지 않을까 싶어서 compiler integration을 넣어봤다. 이를 하기 위해서는 클라이언트 사이드에서 새로운 “command”를 만들어서 등록해주어야 한다.

익스텐션이 활성화되면 실행되는 activate이라는 함수를 구현해주어야 하는데, 여기서 language server와의 커넥션도 초기화해주고 컴파일하는 커맨드도 등록해주면 된다.

package.json에서 “ns.compile” 커맨드에 대한 키 바인딩을 F5로 지정해놨는데, 이 커맨드가 실행되면 registerTextEditorCommand에서 등록된 커맨드 콜백 함수가 호출된다. 우리 같은 경우엔 nsc (nox script compiler) 실행을 하는 함수인 runNsc를 호출한다. 이 함수에서는 ./bin 폴더에 넣어둔 컴파일러 (윈도우에서는 nsc.exe, 리눅스에서는 nsc.linux)를 알맞은 인자를 주고 실행하고 출력 결과를 vscode 내의 아웃풋 채널에다가 출력해준다.

output_compile

(내가 정성들여 컬러코딩한 아웃풋은 안나오지만 ㅜㅜ 그래도 이제 컴파일 하고 싶을때마다 F5를 뙇 눌러주면 바로 아래에 저렇게 컴파일 결과를 보여준다. 컴파일 에러 또한 나타난다.)

Server

이제 좀 더 흥미로운 서버사이드를 봐 보자. 서버에서 지원해야할 것은 크게 세 가지이다: Auto-completion, Signature help, Hover info.

  • Auto-completion: 프로그래머가 소스코드 내에 존재하는 함수명이나 built-in 함수명을 치기 시작하면 해당 글자들을 가지고 있는 함수명들을 보여주고 tab을 눌러 자동완성을 할 수 있게 해주는 기능이다. Built-in 함수인 경우에는 함수 reference에 적힌대로 함수의 설명과 함수의 signature를 보여준다.
    autocomplete
  • Signature help: 특정 함수를 호출하는 코드 작성 시, 그 함수의 signature, 즉 리턴 타입 및 인자 이름과 타입 등을 보여준다. Built-in 함수인 경우에는 현재 작성하는 인자 값을 하이라이팅 해주고 해당 인자에 대한 설명 및 타입을 보여준다.
    signature_help
  • Hoever info: 코드 내의 함수 이름 위에 커서를 올려놓으면 해당 함수의 signature를 보여준다.
    hover

 

익스텐션의 서버 사이드에서는 현재 작업중인 문서들을 담고 있는 TextDocuments 오브젝트의 listen 메소드를 통해 클라이언트/서버의 통신을 위한 IPC 커넥션을 등록한다.

서버가 시작된 후, 클라이언트가 처음 접속할 때 initialize 리퀘스트를 날려서 현재 에디터의 루트 경로와 클라이언트에서 제공하는 capabilities가 뭔지 알려준다. 이 리퀘스트는 IPC 커넥션 오브젝트의 onInitialize 함수를 구현해서 처리할 수 있다. 서버는 이 요청에 대한 응답으로 서버에서 제공하는 capabilities를 알려준다. 우리 같은 경우에는 FULL 싱크 옵션, 코드 컴플리션 제공, signature help 제공, 그리고 마지막으로 hover를 제공한다고 알려준다.

여기서 triggerCharacters는 프로그래머가 함수를 호출하는 코드를 작성중인 경우에 여기에서 정의된 문자를 칠 때 마다 signature helper가 트리거 되야한다고 알려주는 것이다. 함수 호출은 ‘(‘로 시작하고, 각 인자는 ‘,’로 구분되기 때문에 그때 그때 signature를 업데이트 해야하는지 확인해서 클라이언트에게 알려주어야 한다.

Built-in 함수들에 대한 정보는 곳곳에서 쓰이기 때문에 따로 미리 전처리를 해서 보관하고 있는다. 그러기 위해서 이전 글에서 소개했던 builtins.h 파일을 doxygen을 사용해서 xml 파일을 생성하고 만들어진 xml 파일을 간단한 스크립트를 통해 json 형태로 추출한다 (builtins_json).

Auto-completion

자동완성 기능은 onCompletion 핸들러를 구현하면 된다. 클라이언트는 현재 커서의 포지션을 Completion 리퀘스트를 통해 보내고, 서버는 completion 목록에 떠야할 아이템 목록을 리턴하면 된다. 우리가 지원해야할 함수 목록은 built-in 함수들과 프로그래머가 만들어 둔 user-defined 함수들이다. Built-in 함수는 위에서 추출해둔 builtins_json을 사용하면 되기 때문에 미리 만들어 둘 수 있지만, user-defined 함수들은 그때그때 사라질 수도 있고 미리 signature를 가지고 있는게 아니기 때문에 소스코드에서 따로 파싱을 해서 가져와야한다.

또한, Nox Script 3.0의 키워드들을 자동 완성 리스트에 포함시켰다.

Signature Help

시그니쳐 도움말 기능은 onSignatureHelp 핸들러를 구현하면 된다. Completion request와 마찬가지로 클라이언트는 현재 커서의 포지션을 보내고, 서버는 현재 커서의 위치에 따라 알맞은 도움말을 보여주면 된다. 프로그래머가 인자를 넣을 때 마다 알맞은 인자 하이라이팅과 도움말 (타입 정보 및 documentation)을 보여준다. 예를 들어, 위의 스크린샷에서처럼 받는 인자가 3개인 함수 호출을 하는데 두번째 인자 작성 시에는 “float x”에 해당하는 정보를 보여준다.

지금 사용자가 어떤 함수를 호출하려고 하는지, 몇번째 인자를 작성중인지에 대한 정보는 주어지지 않기 때문에 우리가 직접 문서를 파싱해서 알아내야 한다.

리턴은 SignatureHelp 오브젝트이고 가능한 함수 시그니쳐 목록을 나타내는 signatures와 현재 보여져야할 시크니쳐를 나타내는 activeSignature, 그리고 현재 하이라이팅 되어야 할 인자를 나타내는 activeParameter로 구성되어있다. 우리는 함수 오버로딩을 지원하지 않으므로 signatures는 항상 길이 1의 리스트가 되겠다.

프로그래머가 작성한 함수인 user-defined 함수도 마찬가지로 파싱해서 signature help를 지원해줄 수 있다.

Hover Info

마지막으로 추가해줄 기능은 사용자가 특정 함수 이름 위에 마우스 오버를 하면 해당 함수의 signature를 보여주는 기능이다. 빌트인 함수인 경우에는 미리 만들어둔 리스트에서 반환하면 되고, user-defined 함수인 경우에는 해당 함수의 선언문을 파싱해서 반환하면 된다.

 

Packaging

이제 구현을 모두 마쳤으니 패키징을 해서 설치 가능한 extension으로 만들어주어야 한다.

vscode의 익스텐션 창에서 검색해서 나오게 하려면 Visual Studio 마켓플레이스에 공개적으로 익스텐션을 퍼블리쉬해도 되지만, 우리는 필요한 사용자가 설치할 수 있도록 릴리즈를 할 예정이므로 그냥 매뉴얼하게 패키징을 해준다. 다행히도 npm 모듈 중에 익스텐션을 쉽게 패키징 해주는 vsce라는 툴이 있다: npm install -g vsce

vsce 설치가 끝났으면 익스텐션의 서버/클라이언트 모두 빌드를 해주고 client의 package.json이 있는 디렉토리에서 “vsce package”를 실행하면 {name}-{version}.vsix 라는 익스텐션 패키지를 만들어준다. 요놈을 릴리즈하고 사용자는 다운로드 받아서 vscode에서 열거나 드래그/드롭하면 설치가 완료된다.

 

짜잔! 별 작업을 안하고도 새로운 언어인 NoxScript 3.0을 위한 겁나 fancy한 스크립트 에디터가 생겼다!

개발에 사용된 모든 소스코드는 github에 공개되어있다.

 

Leave a Reply

Your email address will not be published. Required fields are marked *