2016/12/12
PowerShellのAST入門
この記事はPowerShell Advent Calendar 2016の11日目です。遅刻してごめんなさい!
ASTとは
ASTとはAbstract Syntax Treeの略で、日本語では「抽象構文木」といいます。コードをパーサーが構文解析した結果から、言語の意味に関係のない要素(空白等)を除外し、木構造として構築したものです。
PowerShellでは3.0からASTの仕組みが取り入れられました。スクリプト実行時にはまずパーサーがスクリプトブロックからASTを生成し、コンパイラによってASTが解釈され、実行されるようになっています。
ASTを直接的に扱うのはコンパイラですが、実はPowerShellではパーサーが構築したASTを、PowerShellスクリプトから扱うことができます。
ASTの具体的な使い道としては、構文の静的解析が挙げられますが、その話は後でするとして、今回はまず、ASTの構成要素と構造を見ていきます。
ASTの構成要素
具体的には、{スクリプトブロック}.Astとして、ScriptblockオブジェクトのAstプロパティから、ScriptBlockAstオブジェクトにアクセスできます。このオブジェクトがASTのルートとなるノード(分岐点)を表します。このScriptBlockAstから、スクリプトブロック内部の構文要素が木構造として展開されていきます。
式(Expression)、文(Statement)といった構文要素は、各々対応したAstクラスが対応し、木構造における分岐点を形成します。また、分岐点の末端の葉では、当該の構文要素を構成するデータを示すオブジェクトが格納されます。
すべてのAstクラスは、Ast抽象クラス(System.Management.Automation.Language.Ast)を継承したクラスです。PowerShellでは50個程のAstクラスが存在します。各Astクラスは、抽象クラスで定義されている以下の2つのプロパティを持っています。
- Parent
親ノードを示すAstオブジェクトを返す - Extent
当該のASTノードに含まれるコード文字列や、スクリプト全体から見たコード文字列の位置等の情報を持つ、IScriptExtentインターフェースを実装したクラスのオブジェクトを返す
また各Astクラスは、対象の構文要素に応じて、それぞれ異なったプロパティを持ちます。たとえばScriptBlockAstは以下のプロパティを持ちます。
子の分岐点を返すもの
- UsingStatements
Using節を表す、UsingStatementAstのコレクションを返す - Attributes
スクリプトブロックに付与された属性を表す、AttributeAstのコレクションを返す - ParamBlock
paramブロックを表す、ParamBlockAstを返す - BeginBlock、ProcessBlock、EndBlock、DynamicParamBlock
各々、beginブロック、processブロック、endブロック、DynamicParamブロックを示すNamedBlockAstを返す
葉を返すもの
- ScriptRequirements
#Requires節の内容を表す、ScriptRequirementsを返す
ASTの構造
たとえば、
$scriptBlock = { param([int]$x,[int]$y) end { $out = $x + $y $out | Write-Host -ForegroundColor Red } }
という、二つの整数値の和を赤字で表示するというスクリプトブロックならば、以下のようなASTが構築されます。(一部分岐点、葉は省略しています。また、分岐点のASTクラス名は、末尾の"Ast"を省略表記しています。)
このスクリプトブロックのASTから、例えば「Red」というパラメータ値を表す、StringConstantExpressionAstまで辿るには、
$scriptBlock.Ast.EndBlock.Statements[1].PipelineElements[1].CommandElements[2]
StringConstantType : BareWord Value : Red StaticType : System.String Extent : Red Parent : Write-Host -ForegroundColor Red
のようにします。
基本的なASTの構造が頭に入っていれば、タブ補完を併用することで比較的簡単に目的のノードまで辿れますが、ASTノードの子に対し、ノード検索をかける方法もあります。
例えば、すべてのVariableExpressionAstを列挙するには、
$scriptBlock.Ast.FindAll({ param($ast) $ast -is [System.Management.Automation.Language.VariableExpressionAst] }, $true)
のように、FindAllメソッドを用います。
AST編はあと何回か続く予定です。
プライバシーポリシー