1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LSPとASTの関係をざっくり理解する

Posted at

VSCodeでPythonを書いてるとき、関数名をCtrl+クリックすると定義元に飛べますよね。あの機能、裏側で何が動いてるか知ってますか?

答えはLSPAST。この2つの関係を、具体例を使ってざっくり解説します。

LSPとは

LSP(Language Server Protocol) は、エディタと言語解析サーバー間の通信プロトコルです。

MicrosoftがVSCode用に作り、今では業界標準になっています。

何が嬉しいの?

LSPが登場する前は、「エディタ × 言語」の組み合わせごとに補完機能などを実装する必要がありました。

  • 10エディタ × 10言語 = 100個の実装が必要...😱

LSPがあると:

  • 言語側:1つのLanguage Serverを作ればOK
  • エディタ側:LSPクライアントを1つ実装すればOK

みんなハッピー!🎉

LSPが提供する機能

  • コード補完(autocomplete)
  • 定義へジャンプ
  • ホバーで型情報表示
  • リネーム(参照を一括置換)
  • エラー・警告表示
  • etc.

ASTとは

AST(Abstract Syntax Tree / 抽象構文木) は、ソースコードをパースして得られる木構造のデータです。

コードの構造を機械が扱いやすい形で表現したもの、と思ってください。

LSPとASTの関係

ここが本題。

ASTは「内部データ構造」、LSPは「通信プロトコル」です。つまり、別物。

ソースコード
    ↓ パース
   AST(Language Server内部で使う)
    ↓ 解析
Language ServerがLSPで結果をエディタに返す

Language Serverの中でASTを使ってコードを解析し、その結果をLSPプロトコルでエディタに伝える、という流れです。

イメージとしては:

  • AST = 「脳内の理解」🧠
  • LSP = 「それを伝える言葉」💬

具体例:Pythonで定義ジャンプ

実際のコードで見てみましょう。

コード

def hello():      # 1行目
    print("hi")   # 2行目

hello()           # 4行目 ← ここでCtrl+クリック

AST(Pylanceが内部で持っている木構造)

Module
├── FunctionDef "hello"  ← 定義: line 1, col 0
│   └── body: Call "print"
│
└── Expr
    └── Call "hello"     ← 参照: line 4, col 0

Pylance(PythonのLanguage Server)はこのASTを見て、「helloという名前は1行目で定義されている」と知っています。

LSPのやり取り

4行目のhello()をCtrl+クリックしたとき、裏ではこんなJSON-RPCが飛んでいます。

① エディタ → Language Server

{
  "method": "textDocument/definition",
  "params": {
    "textDocument": { "uri": "file:///code/main.py" },
    "position": { "line": 3, "character": 0 }
  }
}

「4行目(0-indexed で3)の0文字目にあるhelloの定義はどこ?」

② Language Server → エディタ

{
  "result": {
    "uri": "file:///code/main.py",
    "range": {
      "start": { "line": 0, "character": 4 },
      "end": { "line": 0, "character": 9 }
    }
  }
}

「1行目の4〜9文字目(hello)だよ」

流れまとめ

Ctrl+クリック
    ↓
エディタ: LSPで「3行目0文字目の定義は?」と聞く
    ↓
Pylance: ASTを見てFunctionDef "hello"を発見
    ↓
Pylance: LSPで「0行目4文字目だよ」と返す
    ↓
エディタ: カーソルがピョンと飛ぶ ✨

ASTはいつ作られる?

「コード書くたびにAST作り直してるの?重くない?」と思うかもしれません。

答えは「基本的には変更のたびに走る。ただし賢く最適化されている」です。

タイミング

文字入力
   ↓
デバウンス(数百ms待つ)← 連打してる間は待機
   ↓
変更があった部分だけ再パース
   ↓
AST更新

毎キーストロークではなく、ちょっと手が止まったタイミングで走ります。

最適化のポイント

  1. 増分パース(Incremental Parsing)
    全ファイル再パースではなく、変更箇所の周辺だけ更新する。Tree-sitterなどがこれを得意とする。

  2. ファイル単位のキャッシュ
    開いていないファイルはパース結果をキャッシュしていて、必要になるまで再パースしない。

  3. 遅延解析
    型推論などの重い処理は、実際に必要になるまで後回しにする。

体感できる例

大きなプロジェクトを開いた直後、補完がちょっとモタつくことありませんか?

あれは裏でAST構築中だから。しばらくすると爆速になる=キャッシュができた、ということです。

まとめ

概念 役割
AST コードの構造を表す内部データ。Language Serverの中で使う
LSP エディタとLanguage Server間の通信プロトコル
  • VSCodeの「定義へジャンプ」「補完」「リネーム」などは、裏でLSPが動いている
  • Language ServerはASTを使ってコードを解析し、LSPで結果を返している
  • ASTはコード変更のたびに更新されるが、増分パースやキャッシュで最適化されている

普段何気なく使っている機能の裏側を知ると、エディタがちょっと愛おしくなりますね!

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?