【问题标题】:How can I get the syntax tree from a coderef in Perl?如何从 Perl 中的 coderef 获取语法树?
【发布时间】:2010-10-05 00:10:32
【问题描述】:

我想在 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 构造为一个对象,该对象由具有特定因子所依赖的变量的注释的因子组成。即使这样也可以从代码自省(自动确定变量)中受益。

【问题讨论】:

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


    【解决方案1】:

    对于 optrees 的自省,通常使用 B 系列模块。

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

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

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

    仅使用 optree 内省,您已经可以实现令人惊奇的事情。请参阅 DBIx::Perlish 了解一个非常高级的示例。

    还有一个 B::Generate 模块,它允许构建新的 opttree 来做任何你想做的事情,或者操纵现有的 opttree。但是,B::Generate 并不像人们希望的那样成熟,并且缺少很多功能和不少错误。

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

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

    然而,处理 perl 的 optree 仍然有些繁琐,而且对初学者并不友好。对于任何最神秘的事情,它都不应该是必需的。像使用 B::Deparse->new->coderef2text($cv) 之类的东西,然后可能对评估的源代码进行非常轻微的修改,这真的是我想要从纯 perl 空间进行 optree 内省的方式。

    您可能想退后一步,解释一下您要解决的实际问题。也许有一个更简单的解决方案,根本不涉及与 optree 混淆。

    【讨论】:

    • +1 不错的答案,Scope::Escape::* 看起来很有趣。有什么好的推荐吗?
    • 谢谢。虽然,不幸的是,这对我理解你的真正问题没有多大帮助,这完全是我的错——你的澄清对于有正确背景的人来说似乎很好。因此,对于如何更好地解决您的问题缺乏任何建议,我很乐意帮助您反省您所面临的任何代码。但为此,您必须显示实际代码。
    • 我现在想不出 CPAN 上有任何其他语法插件用户。但是,通常而言,optree munging 是相对常见的。您可能会发现 Parse::Perl 和许多 B::Hooks::OP::Check 依赖项很有趣。之前尝试做的语法插件现在提供的是Devel::Declare。您还会发现很多有趣的模块,它们大多提供新语法,但也提供基于此的新语义。
    • @Eric Strom:另一个示例模块可能是Text::Xslate。我相信这会将模板直接编译为操作码。
    【解决方案2】:

    鉴于您重述的问题 - 我认为您应该在这里做的,而不是尝试使用 coderef,而是尽可能长时间地延迟使用 coderef。

    1. 创建一个代表计算实例的对象。
    2. 为此对象编写评估计算值所需的方法。没有代码生成器,只需以缓慢的方式进行即可。这只是为您提供易于测试并希望易于理解的后续步骤的代码基线。
    3. 编写测试以确保您在第 2 步中所做的事情的正确性。(如果您是这种人,请在第 2 步之前交换此内容。)
    4. 通过编写将计算对象转换为代表同一计算的更优化形式的新对象的方法来实现您在此问题中所要询问的内容。使用您的测试来确保优化后的计算仍然给出正确的结果。
    5. 编写获取计算对象的代码,并生成执行该计算的子(无论是通过字符串eval 还是使用B)。使用您的测试来确保计算在编译后仍然给出正确的结果。

    插入 2 到 5 之间任意位置的可选步骤:

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

    【讨论】:

      【解决方案3】:

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

      my $f = sub { $f1->(@_) * $c->(@_[1 .. 9]) };
      
      $f->(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
      

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

      【讨论】:

      • 其实我想反过来——给定f,确定f1是什么。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多