如何从Perl中的coderef获取语法树?

时间:2010-10-05 00:10:33

标签: perl code-analysis abstract-syntax-tree bytecode-manipulation

我想在Perl中检查和操作任意Perl程序的代码(由coderefs获取)。有没有工具/模块/库?类似于B::Concise的东西,除了B :: Concise在输出上打印代码,但我想以编程方式检查它。

我想像这样使用它。给定一个coderef F,例如。有10个参数:

$ret = &$F(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10);

我想创建一个函数F1,st。

&$F(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) == 
  &$F1(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)*
  &$C(x2, x3, x4, x5, x6, x7, x8, x9, x10)

是将它“分解”为两部分,其中第二部分不依赖x1而第一部分尽可能简单(我假设F被构造为一个巨大的产品)

我想要的应用程序是Metropolis sampling algorithm的优化 - 假设我正在对分布p(x1 | x2 = X1, x3 = X3, ...) = f(x1, x2, x3, ...)进行抽样。算法本身是不变的。乘法常数因子和其他变量不会通过算法改变,因此不需要依赖x1(即上面的$c)的部分不需要进行评估。

联合概率可能有例如。以下表格:

  p(x1, x2, x3, x4, x5) = g1(x1, x2)*g2(x2, x3)*g3(x3, x4)*g4(x4, x5)*g5(x4, x1)*g6(x5, x1)

我还考虑将p构建为一个对象,该对象由具有特定因子所依赖的变量的注释的因子组成。即使这样也会受益于代码内省(自动确定变量)。

3 个答案:

答案 0 :(得分:9)

为了反省内容,通常会使用B系列模块。

给定代码引用$cv,首先为其创建一个B对象:

my $b_cv = B::svref_2object($cv);

现在,您可以调用B中记录的各种方法来从optree中检索各种内容。

仅使用optree内省,您已经可以实现惊人的功能。有关此问题的一个非常高级的示例,请参阅DBIx::Perlish

还有一个B::Generate模块,允许建立新的选择,无论你想做什么,或操纵现有的选择。但是,B::Generate并不像人们希望的那样成熟,并且有很多缺失的功能和相当多的错误。

实际的optree创建和操作通常最好使用perl的C api完成,如perlapiperlgutsperlhack中所述。你可能还需要学习一些XS,以便将你写回的perree操作函数暴露给perl空间,但这真的很容易。

建立optrees(不一定基于其他现有的自我反馈)似乎最近变得有些流行,特别是因为Syntax Plugins已添加到perl 5.12.0的核心中。您可以在cpan上找到各种示例,例如Scope::Escape::Sugar

然而,处理perl的选择仍然有点繁琐,并不完全适合初学者。它不应该是任何最神秘的东西。使用B::Deparse->new->coderef2text($cv)之类的东西,然后使用评估的源代码稍微修改一下,就像我想要从纯perl空间进行optree内省一样。

您可能想稍微退一步并解释您尝试解决的实际问题。也许有一个更简单的解决方案,根本不涉及搞乱选择。

答案 1 :(得分:1)

考虑到你重申的问题 - 我认为你应该在这里做的,而不是尝试munge coderefs,就是尽可能延迟使用coderef。

  1. 创建一个表示计算实例的对象。
  2. 在评估计算值所需的此对象上编写方法。没有codegen,只是做很慢的方式。这只是为您提供了下一步的代码基线,这些步骤易于测试并且希望能够轻松理解。
  3. 编写测试以确保您在步骤2中所做的正确。(如果您是那种人,请在步骤2之前将其交换。)
  4. 通过编写将计算对象转换为表示同一计算的更优化形式的新对象的方法,实现您在此问题中所要求的内容。使用您的测试确保计算在优化后仍能提供正确的结果。
  5. 编写采用计算对象的代码,并生成执行该计算的子(通过字符串eval或使用B)。使用您的测试来确保计算在编译后仍能提供正确的结果。
  6. 可插入2到5之间任意位置的可选步骤:

    • 写一些语法糖(可能使用overload,但也可以使用其他工具)让你使用类似于计算本身的漂亮表达式来构造“计算对象”,而不是大量的对象构造函数

答案 2 :(得分:0)

Perl 5不允许你像这样动态操作字节码,但你可以创建匿名函数。如果我正确理解您的示例,并且我怀疑我这样做,那么您已经有$f1$c引用的两个函数,并且您想要创建一个新的引用$f前两个的结果相互乘以。这很简单:

my $f = sub { $f1->(@_) * $c->(@_[1 .. 9]) };

$f->(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

请注意,使用箭头运算符而不是&来取消引用coderef。这种风格更为常见(在我看来更具可读性)。