構文解析 (AST) と意味解析
切り分けられたフラットなトークンの列は、このままではプログラムの入れ子構造(ブロック構造や数式の結合優先度)を表現できていません。トークン同士の文法関係を解析してツリー構造へとマッピングする「構文解析」と、文法的には正しくても「型」や「定義」に矛盾がないかを検査する「意味解析」の仕組みを学びます。
1. 抽象構文木(AST: Abstract Syntax Tree)とは
構文解析器(パーサー)の主な役割は、入力されたトークンの並びから、ソースコードの論理的な構造を表現した木構造データである「抽象構文木(AST)」を構築することです。
「抽象」と呼ばれるのは、ソースコード中に記述されている括弧 () やコロン :、セミコロン ; といった文法上の区切り文字のうち、木構造そのものによって表現できる冗長な情報を削ぎ落とし、純粋な演算子と被演算子(オペランド)の関係だけを抽出しているためです。
この木構造により、演算の優先順位(足し算 + よりも掛け算 * が先に評価されること)が階層の深さによって明示的に表現されています。
2. 文法の定義(BNF記法)と構文解析の手法
プログラミング言語の文法は、通常、バッカス・ナウア記法(BNF: Backus-Naur Form)などのルール群によって厳密に定義されます。 例えば、単純な四則演算の文法ルールは次のように記述されます。
<Expr> ::= <Term> ( ("+" | "-") <Term> )*
<Term> ::= <Factor> ( ("*" | "/") <Factor> )*
<Factor> ::= NUMBER | IDENTIFIER | "(" <Expr> ")"
この再帰的な文法定義に沿って、プログラムは再帰下降構文解析(Recursive Descent Parsing)などのアプローチで処理されます。
再帰下降パーサーは、各文法カテゴリ(Expr や Term)に対応する関数をプログラム内に用意し、次のトークンを先読みしながらこれらの関数を相互に再帰的に呼び出すことで、自然にツリーを構築していきます。
3. 意味解析(Semantic Analysis)の役割
構文解析によって文法的に正しいASTが完成しても、それがプログラミング言語として実質的に動作可能であるとは限りません。 例えば、次のようなコードは文法(構文)としては正しいですが、意味的に矛盾が生じています。
let name = "Alice";
let result = name * 10; // 文字列と数値の乗算 この「意味的(ロジック的)な適合性」を検査するフェーズが意味解析です。意味解析器はASTを巡回し、以下のチェックを行います。
- 静的型チェック(Type Checking):
演算子や関数のパラメータに渡されたデータの「型(Type)」が適合しているかを検査します(例:整数型と文字列型の乗算を禁止する)。 - 宣言とスコープの解決(Scope Resolution):
参照されている変数や関数が、事前に定義されているか、またその場所からアクセス可能なスコープ内にあるかをシンボルテーブルを用いて検証します。
意味解析を通過したASTは、いよいよターゲットコード(アセンブリ、機械語、または中間コード)を出力する「コード生成」と、無駄な計算を省く「最適化」の段階へと移行します。次節では、この最終工程と、Webの新しい実行規格について学びます。