欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Powershell ISE的抽象语法树编程示例

程序员文章站 2022-03-14 08:01:11
有一个让我非常喜欢windows powershell ise的理由,就是它将它的基础脚本对象模型暴露给用户,这样就允许用户按照自己的方式和需要去自定义脚本体验。 自定义...

有一个让我非常喜欢windows powershell ise的理由,就是它将它的基础脚本对象模型暴露给用户,这样就允许用户按照自己的方式和需要去自定义脚本体验。
自定义ise的核心是$psise对象。$psise对象允许用户去控制ise许多方面的功能。你可以从获取关于$psise的分层对象模型的介绍,和与这些对象相关联的功能。

这篇文章会讨论你怎样利用powershell公开提供的解释器接口,来结合ise对象模型魅力,去创建脚本分析和快速定位的工具。

想象一下,你不得不分析一个相对庞大的powershell脚本。那这个脚本可能是别人写的,也有可能是你自己几个月前写的,扔了好久了。powershell ise已经做了件非常棒的工作了,它提供了脚本环境。你可以通过添加add-on(附加工具)来扩充它的功能,让你的脚本体验更好,更高效。从powershell 3.0开始,脚本的抽象语法树(ast)就可以使用语法解释器接口非常方便的获取了。下面的脚本行会获取当前打开的ise中的脚本的ast:

复制代码 代码如下:

$abstractsyntaxtree = [system.management.automation.language.parser]::
parseinput($psise.currentfile.editor.text, [ref]$null, [ref]$null)

接下来让我们查询脚本中所有的函数:

复制代码 代码如下:

$functionsinfile = $abstractsyntaxtree.findall({$args[0] -is
 [system.management.automation.language.functiondefinitionast]}, $true)

撇开函数定位的定义,如果我们能回到光标之前出现的位置,那将太漂亮了。实现这个也非常简单。我们所要做的只是存储这些行号,然后按照反转顺序反转他们。(是否有人已经知道了,“堆栈”)

下面的脚本块展示了展示了go-to definition的实现。

复制代码 代码如下:

#define some useful global variables
 
$global:__isegotoaddoncurrline=1
 
$global:__isegotoaddoncurrcol=1
 
$global:__isegotoaddonlinetogoto=1
 
$global:__isegotoaddoncoltogoto=1
 
#we need two stacks - one each for line and column
 
$global:__isegotoaddonstackofline = new-object system.collections.stack
 
$global:__isegotoaddonstackofcol = new-object system.collections.stack
 
#this script block has the logic for the implementation of the go-to definition functionality
 
$global:__isegotoaddonscriptblockgoto =
 
{
 
$abstractsyntaxtree =[system.management.automation.language.parser]::parseinput($psise.currentfile.editor.text,[ref]$null, [ref]$null)
 
$functionsinfile = $abstractsyntaxtree.findall(
 
{$args[0] -is[system.management.automation.language.functiondefinitionast]}, $true)
 
#get the text of the line where we have the cursor
 
$str = $psise.currentfile.editor.caretlinetext
 
#store them on the stack for later use
 
$global:__isegotoaddonstackofline.push($psise.currentfile.editor.caretline)
 
$global:__isegotoaddonstackofcol.push($psise.currentfile.editor.caretcolumn)
 
$global:__isegotoaddoncurrline = $global:__isegotoaddonstackofline.peek()
 
$global:__isegotoaddoncurrcol = $global:__isegotoaddonstackofcol.peek()
 
#get the selected text so that it can be used for searching existing functions
 
$selectedfunction = $psise.currentfile.editor.selectedtext
 
#ensure that the cursor is somewhere between the word boundaries of the function
 
$functionsinfile | %{if(($str.contains($_.name)) `
 
–and ($global:__isegotoaddoncurrcol -ge
 
$str.indexof($_.name)) `
 
-and ($global:__isegotoaddoncurrcol -le
 
($str.indexof($_.name)+$_.name.length))
 
)
 
{$selectedfunction = $_.name}
 
}
 
if($selectedfunction -ne "")
 
{
 
#see if the selected function exists in the current open file
 
$functiontogoto = $functionsinfile | ?{$_.name -eq "$selectedfunction"}
 
$global:__isegotoaddonlinetogoto = $functiontogoto.extent.startlinenumber
 
$global:__isegotoaddoncoltogoto = $functiontogoto.extent.startcolumnnumber
 
}
 
if($functiontogoto -eq $null)
 
{
 
try
 
{
 
$comm = get-command -name "$selectedfunction" -erroraction silentlycontinue
 
$comm.definition | out-gridview
 
}
 
catch [system.exception]
 
{
 
}
 
}
 
else
 
{
 
#select the function definition, assuming the function name immediately follows the keyword 'function'
 
try
 
{
 
$psise.currentfile.editor.select($global:__isegotoaddonlinetogoto,
 
($global:__isegotoaddoncoltogoto+9),
 
$global:__isegotoaddonlinetogoto,
 
($global:__isegotoaddoncoltogoto+8+$selectedfunction.length+1))
 
}
 
catch [system.exception]
 
{
 
}
 
}
 
}

补充一下,go-to definition 功能,如果当前powershell会话中存在的话,以上脚本会显示选中文本的定义。(另外,上面的脚本只是一个简单的例子,假如你的“function”关键字和函数名出现在脚本的同一行。这在powershell中并不是必须的,所以如果你的脚本风格不同,你可能需要微调一下逻辑。)

接下来应当是在add-on(附加工具)菜单上添加这些脚本,并把它作为选中脚本的一个命令。下面两行就可以做这件事。

复制代码 代码如下:

$global:__isegotoaddonsb1 =
{& $global:__isegotoaddonscriptblockgoto | out-null}
$null=$psise.currentpowershelltab.addonsmenu.submenus.add(
"go do definition", $global:__isegotoaddonsb1, "f12")

现在来看看我们怎样实现go-back 功能,使用我们定义的全局堆栈,几行代码即可:

复制代码 代码如下:

$global:__isegotoaddonscriptblockgoback =
 
{
 
try
 
{
 
#pop the line and column numbers from the stack to do a reverse traversal
 
$global:__isegotoaddoncurrline =
 
$global:__isegotoaddonstackofline.pop()
 
$global:__isegotoaddoncurrcol =
 
$global:__isegotoaddonstackofcol.pop()
 
$psise.currentfile.editor.setcaretposition(
 
$global:__isegotoaddoncurrline, $global:__isegotoaddoncurrcol)
 
$psise.currentfile.editor.selectcaretline();
 
}
 
catch [system.exception]
 
{
 
}
 
}
 
$global:__isegotoaddonsb2 = {& $global:__isegotoaddonscriptblockgoback | out-null}
 
$null=$psise.currentpowershelltab.addonsmenu.submenus.add("go back",$global:__isegotoaddonsb2, "shift+f12")

就到这里了,只用了一些powershell代码就实现了visual studio中的go-to definition (转向定义)和go-back(返回)功能。

你还可以继续扩展这个脚本,让它包含这些任务:诸如显示脚本中所有函数,点击函数转到函数定义。作为大家进一步扩展功能的鼓励,我给你看下我的 ise附加工具现在的样子。

Powershell ISE的抽象语法树编程示例

扩展powershell ise 中的 “附加工具”菜单