LSPに対する各言語処理系のアプローチ
抱負、2023年はrust-analyzerを真面目に深堀りしたい
TypeScript
コンパイラのコアをLanguage Serviceが呼び出す形
パーサは手書きの再帰下降パーサ
インクリメンタルパーサは全体をパースするが、パース後のASTを再利用してる?
コンパイラは全体がLazilyに作られており、Language Serviceが必要とする情報に関連する情報だけ計算される
Checker は意味解析のほぼ全てを担う実装 (意味解析の一段階目、 Binder によるbindingの後のパス) だが、 Checker はLanguage Serviceが何らかの要求を発するたび生成される
コンパイラ全体はLazilyに作られているためコストは小さい
基本的な型の生成と、グローバルなシンボルテーブルを Binder に構築させる必要はある
Binder は読んでないがシンボルテーブルを得ることが目的ならパーサの時点でどこに差分が生じるかは得られているはずなのでそれを活用できそう
Go
The base libraries for Go (things like go/token, go/ast and go/types) are all designed for compiler-like applications.
今のところはこれらライブラリを頑張って使ってLSを実現している
最終的には普通に lsp/cache/check.go の packageHandle の実装上で go/types 等の提供してるAPIを呼んでる
goplsはCLIインターフェースを備えるが、long-runningでキャッシュをin-memoryに持つLSとして最適化されている
Cache, Session, Viewという3つのレイヤーで情報を保持している
Cacheは本質的にグローバルな情報 (ファイルシステムとその内容など)
Sessionはエディタとのコネクション上の情報 (編集されたファイルなどがoverlayとして)
View?
This holds the configuration, and the mapping to configured packages.
Sessionと分かれてる理由が実装見てもどうにも腑に落ちなかった、 lsp/general.go で View を作るところを見ると protocol.WorkspaceFolder に対応しているのでGoのライブラリあるいは言語デザインとLSPのプロトコルの兼ね合い上必要になった?
The purpose of this layering is to allow a single editor session to have multiple views active whilst still sharing as much information as possible for efficiency.
Rust