🌟

PHP AST(Abstract Syntax Tree)入門

に公開

ASTとは

AST(抽象構文木)は、PHPコードを「文字列」ではなく「構造を持ったツリー」として表現したものです。
コードは一度、字句解析でトークンに分解され、構文解析でASTに組み立てられます。
つまり、ASTを理解するということは、PHPがコードをどう解釈しているかを見ることと同義です。

PHPに存在する2種類のAST

PHPの世界には二系統のASTがあります。

  1. Zend AST
    PHP本体やCレベルの拡張(php-ast)が使うAST。高速で内部向け。

  2. nikic/php-parser AST
    Composerで導入できるライブラリ。PHPだけでASTを扱えるようにしたもの。
    学習・解析・コード操作をする場合、こちらを使うのが一般的。

今から学ぶのは nikic/php-parser のASTです。

nikic/php-parser ASTの基本構造

このASTでは、すべてのノードが PhpParser\Node を継承しています。
ノードは大きく分けて2種類に分類されます。

Node
├ Stmt(Statement、文)
└ Expr(Expression、式)

Stmtは構造や制御を表し、Exprは値や演算を表します。

例:StmtとExprの違いをコードで見る

if ($x > 0) {
    return $x + 1;
}

if自体は Stmt_If
return も Stmt_Return
しかし $x + 1 は Expr_BinaryOp_Plus という式です

つまり「文の中に式が含まれる」という構造になっています。

実際のAST構造イメージ

function add($x, $y) {
    return $x + $y;
}

これをASTで見ると概念的にはこうなります。

Stmt_Function(name: add)
├ Param(x)
├ Param(y)
└ Stmt_Return
└ Expr_BinaryOp_Plus
├ Expr_Variable(x)
└ Expr_Variable(y)

こうして階層構造として理解するのがASTです。

よく出てくるExprノード

Expr_Variable → $var
Expr_Assign → $a = $b
Expr_MethodCall → $obj->call()
Expr_StaticCall → Class::call()
Expr_New → new A()
Expr_FuncCall → f()
Expr_ArrayDimFetch → $arr[0]
Expr_BinaryOp_* → +, -, ., && などの演算系

よく出てくるStmtノード

Stmt_Function → function foo() {}
Stmt_Class → class Foo {}
Stmt_Interface → interface i {}
Stmt_ClassMethod → public function bar() {}
Stmt_If → if (...) {}
Stmt_Return → return ...;
Stmt_Foreach / Stmt_For → ループ構造

nikic/php-parserを使ってASTを確認する方法

Composerでインストールします。

composer require nikic/php-parser

最小のAST表示コード例

use PhpParser\ParserFactory;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Node;

require 'vendor/autoload.php';

$code = <<<'CODE'
<?php
function add($x, $y) {
    return $x + $y;
}
CODE;

$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
$ast = $parser->parse($code);

$traverser = new NodeTraverser();
$traverser->addVisitor(new class extends NodeVisitorAbstract {
    public function enterNode(Node $node) {
        echo $node->getType(), PHP_EOL;
    }
});

$traverser->traverse($ast);

これを実行すると、ASTノードの種類が1行ずつ出力されます。
これが「コードを木として見る」第一ステップです。

ASTを理解するための軸

コードを構文ツリーとして見る
StmtとExprを区別する
Node名が表す構文を覚える
ASTをダンプして目で見ることで構造が身体で覚えられる

Discussion