Absztrakt szintaxis fa - AST

Egy másik szkriptelemzési lehetőség az Abstract Syntax Tree (AST) használata. Ez egy számítógéptudomány által régebb óta használt ábrázolási módszer, amellyel a programkódot egy gráf formájában ábrázolhatjuk. Ezzel a módszerrel nem kapjuk vissza a teljes forráskódot, mert az némi elemzésen esik keresztül és csak egy absztrakt leképezését kapjuk meg. De pont ez lehet az előnye is, hiszen a sallangoktól mentes, a kód logikáját megragadó eredményt kapunk.

Nézzünk erre egy egyszerű példát:

PS C:\> $sb = {$x = dir | Where-Object {$_.psiscontainer} <#komment#>}

Van ebben egy értékadás, csővezeték, szkripblokk, változó, tulajdonság és egy kis megjegyzés a végén. PowerShell 3.0 felett ennek a szkrtiptblooknak van egy AST tulajdonsága, aminek további tulajdonsága is vannak. Ha csak az értékadást vizsgáljuk a legfelsőbb logikai szinten, akkor azt így kapjuk meg:

PS C:\> $sb.Ast.EndBlock.Statements[0].Left.Extent.Text

$x

PS C:\> $sb.Ast.EndBlock.Statements[0].Right.Extent.Text

dir | Where-Object {$_.psiscontainer}

Az AST-n belül egy kifejezésünk van (Statements[0]), ez az egész kifejezés valójában egy end block. A bal (Left) részben van az $x, az egyenlőség jobb (Right) oldakán a csővezeték.

Ha a jobb oldalt részletesebben vizsgáljuk, akkor láthatjuk, hogy ott van egy PipelineElements tulajdonság is, ami kiadja a csővezetékem első (dir) és második elemét (Where-Object ...).

PS C:\> $sb.Ast.EndBlock.Statements[0].Right.PipelineElements

 

 

CommandElements    : {dir}

InvocationOperator : Unknown

DefiningKeyword    :

DefinedKeywords    :

Redirections       : {}

Extent             : dir

Parent             : dir | Where-Object {$_.psiscontainer}

 

CommandElements    : {Where-Object, {$_.psiscontainer}}

InvocationOperator : Unknown

DefiningKeyword    :

DefinedKeywords    :

Redirections       : {}

Extent             : Where-Object {$_.psiscontainer}

Parent             : dir | Where-Object {$_.psiscontainer}

Ezt még mélyebben is lehet vizsgálni, a CommandElements ezt adja meg, a második eleme például egy szóból áll (Where-Object) és a szkriptblokkból ({$_.psiscontainer}):

PS C:\> $sb.Ast.EndBlock.Statements[0].Right.PipelineElements[1].CommandElement

s

 

 

StringConstantType : BareWord

Value              : Where-Object

StaticType         : System.String

Extent             : Where-Object

Parent             : Where-Object {$_.psiscontainer}

 

ScriptBlock : {$_.psiscontainer}

StaticType  : System.Management.Automation.ScriptBlock

Extent      : {$_.psiscontainer}

Parent      : Where-Object {$_.psiscontainer}

Ha nem akarunk lépésről-lépésre beleásni a tulajdonságok hierarchiájába, akkor az AST FindAll metódusát érdemes használni:

PS C:\> $sb.AST.FindAll({$true},$true)

 

 

Type               : ScriptBlock

ParamBlock         :

BeginBlock         :

ProcessBlock       :

EndBlock           : $x = dir | Where-Object {$_.psiscontainer}

DynamicParamBlock  :

ScriptRequirements :

Extent             : {$x = dir | Where-Object {$_.psiscontainer} <#komment#>}

Parent             : {$x = dir | Where-Object {$_.psiscontainer} <#komment#>}

 

Type       : NamedBlock

Unnamed    : True

BlockKind  : End

Statements : {$x = dir | Where-Object {$_.psiscontainer}}

Traps      :

Extent     : $x = dir | Where-Object {$_.psiscontainer}

Parent     : {$x = dir | Where-Object {$_.psiscontainer} <#komment#>}

 

Type          : AssignmentStatement

Left          : $x

Operator      : Equals

Right         : dir | Where-Object {$_.psiscontainer}

ErrorPosition : =

Extent        : $x = dir | Where-Object {$_.psiscontainer}

Parent        : $x = dir | Where-Object {$_.psiscontainer}

 

Type         : VariableExpression

VariablePath : x

Splatted     : False

StaticType   : System.Object

Extent       : $x

Parent       : $x = dir | Where-Object {$_.psiscontainer}

 

Type             : Pipeline

PipelineElements : {dir, Where-Object {$_.psiscontainer}}

Extent           : dir | Where-Object {$_.psiscontainer}

Parent           : $x = dir | Where-Object {$_.psiscontainer}

 

Type               : Command

CommandElements    : {dir}

InvocationOperator : Unknown

DefiningKeyword    :

DefinedKeywords    :

Redirections       : {}

Extent             : dir

Parent             : dir | Where-Object {$_.psiscontainer}

...

Type         : VariableExpression

VariablePath : _

Splatted     : False

StaticType   : System.Object

Extent       : $_

Parent       : $_.psiscontainer

 

Type               : StringConstantExpression

StringConstantType : BareWord

Value              : psiscontainer

StaticType         : System.String

Extent             : psiscontainer

Parent             : $_.psiscontainer

Kicsit megvágtam a fenti kimenetet a könyvben, mert még 2 oldalt megtöltött volna. Mindenesetre látható ennyiből is, hogy a kód logikájának minden lényeges eleme ábrázolva van. A zárójelek például nincsenek külön kiemelve, mert azt maga az AST elemeinek elrendezése ábrázolja. Természetesen a megjegyzésnek sincs külön eleme.

Mit lehet mindezzel kezdeni? Például kikereshetjük a változókat:

$sb.AST.FindAll({$true},$true) |

    Add-Member -MemberType ScriptProperty -Name Type -Value {

            $this.gettype().fullname -replace "System.Management.Automation.Language.(\w+)AST$",'$1'

        } -PassThru -force | ?{$_.type -eq "VariableExpression"}

 Egy kicsit alakítottam a kimeneten, generáltam egy Type tulajdonságot, ami az elem típusát adja meg. Ezzel a kimenet:

Type         : VariableExpression

VariablePath : x

Splatted     : False

StaticType   : System.Object

Extent       : $x

Parent       : $x = dir | Where-Object {$_.psiscontainer}

 

Type         : VariableExpression

VariablePath : _

Splatted     : False

StaticType   : System.Object

Extent       : $_

Parent       : $_.psiscontainer

És itt kanyarodjuk el a FindAll rejtélyes két paraméteréhez. Az első egy szkriptblokk, ami tulajdonképpen egy szűrőfeltétel. Azokat az elemeket fogja csak megadni a metódus, amelyekre a feltétel igaz lesz. Azaz az előző kifejezést sokkal egyszerűbben is össze tudjuk rakni:

$sb.AST.FindAll(

        {$args[0] -is [System.Management.Automation.Language.VariableExpressionAst]},

        $true

    )

A második paraméter azt jelzi, hogy a beágyazott kifejezésekbe is beleásson-e, vagy csak a legfelső szinten levőket.

 



Word To HTML Converter