AST(抽象構文木)とは?CSTとの違いと走査・変換・実務活用を徹底解説

ASTとは何か(概要)

AST(Abstract Syntax Tree:抽象構文木)は、プログラムの構文構造を木構造で表現したデータ構造です。ソースコードの文法的な要素(式、文、宣言など)をノードとして表し、プログラミング言語の意味解析や変換、コード生成など多くの工程で中心的役割を果たします。単なるテキスト(文字列)では得られない構造化された情報を提供し、コンパイラ、インタプリタ、静的解析器、コード整形ツール、IDEの機能などで広く利用されます。

パースとASTの生成

ASTは通常、字句解析(lexing)と構文解析(parsing)のフェーズを経て生成されます。字句解析でトークン列に分解した後、構文解析器が文法規則に従ってトークンを組み合わせ、まず「構文木(parse tree / concrete syntax tree, CST)」を作成します。CSTは文法のすべての構造(括弧や区切り記号など)を表現しますが、ASTはそれらの冗長な中間ノードや記号を削ぎ落とし、言語意味にとって重要な構造のみを残した抽象化された木です。

生成時には、位置情報(行番号・列番号やバイトオフセット)や型注釈、スコープ情報などをノードに付加することが多く、これが後続処理(エラー報告やデバッグ、ソースマップ生成)に役立ちます。

CST(Concrete Syntax Tree)との違い

  • CST(構文木)は構文規則を忠実に反映する。括弧やセミコロンなどの構文要素もノードとして残る。
  • ASTは言語意味に直接関係のある構造だけを残す。冗長な構文ノードは省略され、ノード名も意味重視(例:BinaryExpression, FunctionDeclaration)となる。
  • ツール選択の観点では、フォーマッタやコード整形はCSTが役に立つ(ホワイトスペースやコメントの位置を正確に扱えるため)、一方で最適化や静的解析、トランスパイルはASTの方が扱いやすい。

ASTの構造と代表的なノード

ASTはツリー構造で、各ノードは種類(ノードタイプ)と子ノード、必要に応じて識別子やリテラル値、位置情報を持ちます。代表的なノード例を言語に依らず一般化すると:

  • Program / Module:ファイルやモジュール全体を表す根ノード
  • FunctionDeclaration / FunctionExpression:関数の宣言や式
  • VariableDeclaration / VariableDeclarator:変数宣言
  • Identifier:名前(変数名、関数名)
  • Literal:数値、文字列、真偽値などの定数
  • BinaryExpression / UnaryExpression:演算子を持つ式
  • CallExpression:関数呼び出し
  • BlockStatement / ReturnStatement / IfStatement / ForStatement:制御構造

ASTの走査と操作(Traversal & Transformation)

ASTを使う上で重要なのは「走査(traversal)」と「変換(transformation)」の手法です。走査は木を深さ優先(DFS)や幅優先(BFS)で巡回し、各ノードに処理を適用します。代表的なデザインパターンに Visitor パターンがあります。Visitorではノードタイプごとの処理を分離でき、解析・最適化・変換を管理しやすくなります。

変換では、ASTを書き換えて新たなASTを生成します。トランスパイル(例:ES6→ES5)、最適化(不要コードの削除)、自動リファクタリング、コード挿入(ログ追加)などが典型的です。変換後にコード生成器がASTからテキスト(ソースコード)を再生成します。ソースマップを用いれば、変換されたコードと元のソースの対応を保持できます。

主な利用用途

  • コンパイラ/インタプリタ:構文解析→意味解析→中間表現→コード生成の中心データ構造。
  • トランスパイラ/トランスフォーム:BabelやTypeScriptのように新しい構文を古い環境向けに変換。
  • 静的解析(リント)/セキュリティ分析:コードの欠陥や脆弱性(例:未使用変数、潜在的なSQLインジェクション)を検出。
  • 自動リファクタリング:リネーム、抽出、インライン化など安全なプログラム変換。
  • IDE機能:補完、ジャンプ先解析、シンボル参照検索、型推論の補助。
  • フォーマッタ・整形:場合によってはCSTが使われるが、意味に基づく整形はAST情報が役立つ。
  • コード計測・カバレッジ:実行経路の挿入や計測ポイントの配置。

代表的なツールとライブラリ

  • Babel / @babel/parser(JavaScript):ASTを生成・変換するエコシステム。ESTree互換のAST形を扱う。
  • Esprima, Acorn(JavaScriptパーサ)
  • AST Explorer(https://astexplorer.net):様々な言語のパーサとASTを可視化できる便利なウェブツール。
  • Tree-sitter(汎用パーサ生成器、インクリメンタルパース対応):エディタ統合に強い。
  • ANTLR(文法定義とパーサ生成)
  • Pythonの標準 ast モジュール(https://docs.python.org/3/library/ast.html)
  • Roslyn(.NETのコンパイラプラットフォーム):C#/VBのAST操作とコード生成をサポート
  • Clang/LLVM の AST(C/C++):libclang/Clang ASTでソース情報を詳しく扱える

実務での注意点・課題

  • コメントやホワイトスペースの扱い:ASTは通常意味素のみを表すため、コメントや細かな書式情報は失われる。フォーマッタや元のスタイルを保つ必要がある場合はCSTやコメントを付随させる仕組みが必要。
  • 動的コード(eval等)とメタプログラミング:実行時に生成されるコードやマクロ、テンプレートは静的ASTだけでは完全に扱えない。
  • 言語仕様の複雑さ:プリプロセッサ(C/C++)、マクロ(Lisp系)、型システム(ジェネリクス、テンプレート)はAST処理を難しくする。
  • パフォーマンスとメモリ:大規模プロジェクトでのAST生成・走査はコストが高く、インクリメンタル解析や差分更新が要求される場面が多い。
  • 互換性と標準化:JavaScriptならESTreeという仕様が一般的だが、言語ごとにASTの形は異なるためツール間での互換性を考慮する必要がある。

実例:JavaScriptのAST(ESTree)を使ったワークフローの概念

典型的なワークフローは次の通りです:ソース→パーサ(@babel/parser/Esprima)→AST→解析・変換(プラグイン/visitor)→コード生成(@babel/generator)→出力。変換中に位置情報を保ち、ソースマップを生成することでデバッグ時に元のソースと照合できます。

まとめ

ASTはプログラムの構造を抽象化して表現するための基本データ構造で、コンパイラやツールチェーンの中核を成します。構文木(CST)と区別しつつ、走査・変換・再生成のパイプラインを理解することが、静的解析、トランスパイリング、リファクタリングなどの実装に不可欠です。実務ではコメント保全、動的コード対応、パフォーマンス対策などの課題にも注意を払う必要があります。

参考文献