組み込みスクリプト言語 AngelScript の Language Server を作った話
KMC Advent Calender 2024 の 22 日目の記事です。昨日の記事は C105でインフラ本を出します でした!
作ったもの
VSCode の AngelScript Language Server 拡張機能です。
背景
C++ / Siv3D で開発する新作ゲーム[1]に向けて、UI やゲームロジック実装を円滑化する仕組みを検討していました。このような場合の有用な手法の一つとして、組み込みスクリプト言語を使用することが考えられます。
C++ における組み込みスクリプト言語といえば、真っ先に挙がる候補は Lua だと思います。Lua は高速で軽量なスクリプト言語ではあるのですが、次のような欠点に苦しめられたことがありました。
- 配列が 1-based index (配列の先頭要素へアクセスするときに 0 ではなく1 を使う)
- 動的型付けによる意図しないエラー
- バインドされた C / C++ API が IDE でコード補完されない
そこで、Lua の代替のスクリプト言語として目を向けたものが AngelScript でした。AngelScript は Siv3D に標準で組み込まれている上、以下に挙げられる強みがあることが分かりました。
- 静的型付けでコードの保守性が高まる
- C++ や C# に似た文法で取っつきやすい
- アプリケーションに応じて柔軟にカスタマイズが可能
実例を調べると、数年前遊んだ話題になったゲーム『It Takes Two』で AngelScript が取り入れられていることが分かりました。[2] It Takes Two は UE4 で開発されたゲームであり、UE4 には Unreal Engine Angelscript という名で AngelScript プラグインが存在しているようでした。
このように、Lua にない強みがあり実績もある程度存在する AngelScript を導入しようと思い立ったのですが、差し当たって重大な問題が一つあることが分かりました。それは、マイナーな言語ゆえに VSCode で AngelScript 用の拡張機能がほとんど存在しなかったことです。プラグインによる開発支援が行われず、定義ジャンプやリネーム機能、補完機能などが一切使えない状況でした。現代の IDE に慣れたプログラマにとって、それらの文明の利器が使えないことはコーディング体験が最悪のものとなりそうです。
こうした経緯から、まずは自分で AngelScript のプラグインを開発する必要がありそうという結論に至りました。
Language Server Protocol とは何なのか
特定のプログラミング言語用プラグイン開発の調査にあたり、まず Language Server Protocol (LSP) という規格があることを知りました。
LSP に基づいて Language Server というものを実装すると、開発者は IDE に依らず任意のエディタでプログラミング言語のサポートを受けることが可能になるようです。IDE のバックエンド側の機構みたいなものなようです。
差し当たり AngelScript の Language Server の開発をすることにしました。
Language Server の開発
参考になった記事
検索エンジンで情報を探してみたところ、Language Server 実装に関する記事は数多く存在していました。まず、以下の記事を拝読しました。
この記事により、自作プログラミング言語で Language Server プラグインを開発する全体像が大まかに把握出来ました。以下の流れで実装を進めていけば良さそうということが見えました。
- クライアント (IDE 本体) から「ファイル編集」といったイベントを受信する
- ファイルをコンパイルする
- 字句解析
- 構文解析
- 意味解析
- コンパイルされた情報に基き、リクエストに応じたリスポンスをクライアントへ返す
記事にならって IDE はひとまず VSCode に決め打ちすることにしました。
また、クラシックな C++ で書かれた AngelScript ソースコードを Language Server に改造するのはかなり高コストな作業になりそうだったので、TypeScript で一から自分で実装することにしました。今回のようにコンパイラ部分を再実装する方式は割と一般的であるようです。[3]
公式サンプル
VSCode の拡張機能開発について調べてみると、MIT ライセンスで公開されているMicrosoft 社公式のサンプルが存在することが分かりました。
このリポジトリには lsp-sample という Language Server の最低限な実装例が含まれていたので、これに基づいて AngelScript Language Server を開発することにしました。
実装
上記の lsp-sample を土台に、まずは AngelScript のコンパイラ (フォワード部分) を TypeScript で実装を始めました。字句解析 (tokenizer.ts)、構文解析 (parser.ts)、意味解析 (analyzxer.ts) を順に書いていくことにしました。AngelScript 本家 C++ ソースコード及び BNF を参考にしました。
コンパイラ部分の実装がある程度終わったら、定義ジャンプやリネーム機能などを一つずつ対応させていきました。
また、C++ でバインドした API を定義出来るようにする as.predefined
といった独自仕様も実装しました。
ある程度完成したところで v0.1.0 として Visual Studio Marketplace の方に公開しました。
感想と反省
ジェネリックといった実装に手間のかかる部分も存在する AngelScript の BNF に対応した パーサーを書き下していくことは予想以上に骨の折れる作業でした。しかし、一からパーサーを書く経験によりコンパイラの理解がまた深まったと感じています。
自作コンパイラの開発と違い、自作 Language Server は今後の開発で実際に役に立つソフトウェアという点に意義があると思いました。マーケットプレイスに公開した後、実際に他の人の役になっているということも嬉しい限りです。
ただ、今回 AngelScript C++ ソースコードの実装をあまり見ずに、独自の手法で適当にやってしまった部分がかなり多くなってしまった点について反省しています。Language Server の実装にいくつか問題がある箇所が散見しており、「エディタではエラーが出ているのに実際に動かすとコンパイルが通る!」[4] という望ましくない挙動がたまに発生します。これについては追々直していけたらと思っています。
開発後の運用について…
さて、ある程度使い物になる AngelScript Language Server が完成したのですが、残念ながら新作ゲーム開発において AngelScript を導入する必要が無くなりました 🥲
その理由は、Live++ という技術革新的な画期的開発ツールと出会ってしまったことです。Live++ をプロジェクトに取り入れた結果、開発においてスクリプト言語を採用する余地がなくなってしまいました。これについては以前の記事で解説しています。
とはいえ、ユーザーにアプリケーションの MOD や拡張機能開発用の API としてスクリプト言語を運用することは今後も存続するはずなので、依然として AngelScript には一定の価値があると思います。最近は AngelScript Language Server の開発をあまりやっていないのですが、いずれまた再開していけたらと思っています。
おわりに
今回の経験を通して Language Server は自分でも開発出来るということが分かりました。みなさんもお好きな言語の Language Server をぜひ自作してみてください。
また、AngelScript 開発コミュニティが一層盛り上がることを願っています。
Discussion