본문 바로가기

javascript/nodejs

[오늘의 삽질] web-tree-sitter 과 emscripten

https://tree-sitter.github.io/tree-sitter/

 

Tree-sitter|Introduction

Introduction Tree-sitter is a parser generator tool and an incremental parsing library. It can build a concrete syntax tree for a source file and efficiently update the syntax tree as the source file is edited. Tree-sitter aims to be: General enough to par

tree-sitter.github.io

 tree-sitter은 C언어 기반의 파서 생성기? 로 의존성을 최소화하여 다양한 환경에서 동작하는 것을 목표로 하고 있는 라이브러리이다. 기본적으로 다양한 언어에 대한 CST(Concrete Syntax Tree) 을 제공하며 다양한 언어에 대한 바인딩이 존재하므로, 자신에게 익숙한 언어 바인딩을 이용하여 다양한 언어를 CST를 토대로 분석할 수 있게 된다.

공식 문서 : https://github.com/tree-sitter/tree-sitter/blob/master/lib/binding_web/README.md

npm에서의 위치 : https://www.npmjs.com/package/web-tree-sitter 

 웹 환경에서도 tree-sitter 라이브러리를 이용할 수 있도록 web-tree-sitter 이라는 라이브러리가 별도로 존재한다. 특히 해당 모듈은 현재 vscode.dev 에서 문법을 분석하는데 사용되는 anycode 확장의 주된 부분이 된다. vscode.dev은 Worker API을 기반으로 동작하도록 구현되어 있으므로 모든 코드가 웹 상에서 동작해야 한다. 따라서 사용 가능한 언어는 자바스크립트와 wasm 으로 제한된다. 

 tree-sitter에서 미리 만들어 둔 여러가지 파서들은 tree-sitter-~ 의 형태로 npm에서 설치할 수 있다. 설치된 상태에서는 wasm 바인딩을 위한 것은 아니므로, tree-sitter 라이브러리 자체에서만 사용할 수 있다.

 vscode.dev에서는 web-tree-sitter의 wasm 바인딩 버전을 이용한다. web-tree-sitter 모듈은 wasm 바인딩이 이미 되어있는 상태이므로 그대로 사용해도 되지만, 다양한 언어에 대한 파서들은 wasm 코드로 변환되어있지 않으므로, emscripten 이라는 wasm 변환 라이브러리를 이용하여 wasm으로 변환해야 한다.

 리눅스 환경에서는 emscripten을 다음과 같이 설치한다

git clone https://github.com/emscripten-core/emsdk.git emsdk
cd emsdk
./emsdk install latest # 최신버전 설치
./emsdk activate latest # 최신 버전 activate
source ./emsdk_env.sh # 잠깐 환경변수에 emcc 등록

emcc --version # emscripten 라이브러리가 설치되어 있는지 볼 수 있음!

 맨 아래 명령어를 수행했는데 오류가 발생한다면 제대로 설치되지 않은 것이므로 다시 설치하자. 

 여기까지 오면 tree-sitter 기반 언어 파서들을 wasm 으로 변환하는 것은 어렵지 않다. 다음 과정을 따르자.

# 어떤 폴더에 가서
npm init # npm 프로젝트 초기화
npm install tree-sitter- ~ # 원하는 언어에 대한 파서 다운로드
npm install tree-sitter-cli # cli 설치

npx tree-sitter build-wasm node_modules/tree-sitter- ~ # wasm으로 변환

이렇게 하면 해당 폴더에 wasm 파일이 생성된다.

생성된 wasm 파일을 실제로 사용할 프로젝트로 가져가서 다음과 같이 사용하면 된다.

Parser.init({
    locateFile() {
        return path.resolve('node_modules/web-tree-sitter/tree-sitter.wasm');
    }
}).then(() => {
    console.log("초기화 성공!");
}).catch((e) => {
    console.log(e);
});
const parser = new Parser();
let lang;
Parser.Language.load(path.resolve('tree-sitter-python.wasm'))
    .then((v) => {
        lang = v;
    });
parser.setLanguage(lang);
const tree = parser.parse(`
x = 10
y = 12
z = x + y`);

 그런데 실제로 이러한 코드를 실행하면 제대로 동작하지 않는다. 정확히는 Assertion failed 에러가 발생했다.

https://github.com/tree-sitter/tree-sitter/issues?q=assertion+failed 

 web-tree-sitter에서 내용을 검색해보니, 나와 유사한 현상을 겪는 사람이 매우 많았다. 원인은 다음과 같다.

emscripten 2.0.17 버전 이상에서 언어 파서를 wasm으로 파싱하면 오류가 발생한다

 대략 해당 라이브러리에서 사용하는 파이썬 버전이나 코드가 변경되면서 tree-sitter 의 언어 파서 라이브러리들과 제대로 대응되지 않는 현상이 있으며, 이런 문제가 현재까지도 수정되지 않고 이어지면서 wasm 파일이 일부 깨진 상태로 생성되어 공식 문서만 따라하게 되면 Language.load 수준에서 에러가 발생하여 제대로 실행할 수 없는 것이다.

 해당 현상은 단순히 emscripten 의 버전을 2.0.17로 내려 명령을 수행하는 것으로 해결된다고 한다.

git clone https://github.com/emscripten-core/emsdk.git emsdk # 깃 클론
cd emsdk # 폴더로 이동

git checkout 2.0.17 # 2.0.17 버전으로 이동

./emsdk install latest # 최신버전 설치
./emsdk activate latest # 최신 버전 activate
source ./emsdk_env.sh # 잠깐 환경변수에 emcc 등록

emcc --version # emscripten 라이브러리가 설치되어 있는지 볼 수 있음!

실제로 동작한다! ( server.ts:60 참고 )

이 글을 작성하는 시점에서 web-tree-sitter의 공식 문서는 2020년 이후로 제대로 수정되지 않고 있다. 따라서 해당 시기에는 emscripten과 호환되던 라이브러리들이 이제는 호환되지 않음에도, 이를 공식 문서에서는 반영하지 않고 있는 것이다. 사실 맨 처음에는 라이브러리 자체에 익숙하지 않아 당연히 나의 문제라고 생각했는데, 버전 차이에 의해서도 이런 문제가 발생할 수 있다는 것이 정말 마음속 깊이 와닿았다. 버전 관리가 이렇게 중요하다니... 

 좌절과는 별개로 wasm이라는 것이 어떻게 사용되는지 궁금했는데, C언어 기반의 라이브러리가 실제로 웹 상에서 동작할 수 있도록 wasm으로 변환하고 실제로 사용하는 모습을 보니, 참 신기하다는 생각이 든다.

참고한 문서들

실제 개발 내용

https://github.com/blaxsior/web-lint-test

 

GitHub - blaxsior/web-lint-test

Contribute to blaxsior/web-lint-test development by creating an account on GitHub.

github.com

 개별연구 과목에서 web-tree-sitter을 이용하여 vscode dev 플러그인을 개발했다. 1주일 정도 삽질하여 개발 당시에는 스트레스를 받았으나, 새로운 분야 및 기술을 접할 기회가 되어 재미있었던 것 같다.