用于转换节点树的库

时间:2012-01-18 21:28:28

标签: python refactoring tree expression-trees nodes

我希望能够在不写一堆重复的意大利面条代码的情况下表达一棵树到另一棵树的一般转换。有没有图书馆来帮助解决这个问题?我的目标语言是Python,但只要可以移植到Python,我就会查看其他语言。

示例:我想转换此节点树:(请原谅S-expressions

(A (B) (C) (D))

进入这一个:

(C (B) (D))

只要父母是A而第二祖先是C,无论上下文如何(可能有更多的父母或祖先)。我想以简单,简洁和可重用的方式表达这种转变。当然这个例子非常具体。请尝试解决一般情况。

编辑:RefactoringNG是我正在寻找的东西,虽然它引入了一个全新的语法来解决问题,我想避免。我仍在寻找更多和/或更好的例子。


背景:

我能够将python和cheetah(不要问!)文件转换为标记化的树表示形式,然后将它们转换为lxml树。我计划重新组织树并写出结果,以实现自动重构。 XSLT似乎是重写XML的标准工具,但语法很糟糕(在我看来,很明显),我们店里的任何人都不会理解它。

我可以编写一些函数,只使用lxml方法(.xpath等)来实现我的重构,但是我担心我会结束一堆特制的意大利面条代码,这些代码无法重新编写 - 二手。

2 个答案:

答案 0 :(得分:2)

让我们在Python代码中尝试这个。我已经在叶子上使用了字符串,但这适用于任何对象。

def lift_middle_child(in_tree):
    (A, (B,), (C,), (D,)) = in_tree

    return (C, (B,), (D,))

print lift_middle_child(('A', ('B',), ('C',), ('D',))) # could use lists too

这种树转换通常在功能样式中更好地执行 - 如果你创建了一堆这些函数,你可以明确地组合它们,或者创建一个合成函数来以无点样式处理它们。

因为你已经使用了s表达式,所以我认为你很乐意将树表示为嵌套列表(或等价物 - 除非我错了,lxml节点可以这种方式迭代)。显然,这个例子依赖于一个已知的输入结构,但你的问题暗示了这一点。只要具有这种统一的界面,您就可以编写更灵活的函数,并且仍然可以编写它们。

以下是行动中的代码:http://ideone.com/02Uv0i

现在,这里有一个反转孩子的功能,并使用它和上面的功能,一个提升和反转:

def compose2(a,b): # might want to get this from the functional library
    return lambda *x: a(b(*x))

def compose(*funcs): #compose(a,b,c) = a(b(c(x))) - you might want to reverse that
    return reduce(compose2,funcs)

def reverse_children(in_tree):
    return in_tree[0:1] + in_tree[1:][::-1] # slightly cryptic, but works for anything subscriptable

lift_and_reverse = compose(reverse_children,lift_middle_child) # right most function applied first - if you find this confusing, reverse order in compose function.

print lift_and_reverse(('A', ('B',), ('C',), ('D',)))

答案 1 :(得分:1)

你真正想要的是恕我直言program transformation system,它允许你使用源代码的表面语法中表达的模式(甚至是目标语言)来解析和转换代码直接表达重写。

你会发现,即使你可以掌握Python树的XML表示,编写XSLT / XPath转换的努力也超出了你的预期;表示真实代码的树比你期望的更麻烦,XSLT并不是那么方便的符号,它不能直接表达你想要检查的树上的常见条件(例如,两个子树是相同的)。 XML的最终复杂性:假设它已被转换。如何重新生成源代码语法?你需要某种漂亮的打印机。

无论代码如何表示,一般的问题是没有关于范围和类型的信息(在哪里可以得到它),编写正确的转换非常困难。毕竟,如果您要将python转换为使用不同运算符进行字符串连接和算术的语言(与使用“+”的Java不同),您需要能够决定生成哪个运算符。所以你需要输入类型信息来决定。 Python可以说是无类型的,但实际上大多数表达式涉及的变量在其整个生命周期中只有一种类型。因此,您还需要流量分析来计算类型。

我们的DMS Software Reengineering Toolkit拥有所有这些功能(解析,流分析,模式匹配/重写,漂亮打印)和robust parsers,适用于包括Python在内的多种语言。 (虽然它具有针对C,COBOL,Java实例化的流分析功能,但这并没有为Python实例化。但是,你说你想要进行转换而不考虑上下文。)

要在Python语法中表达你的Python语法重写,接近你的例子(不是Python?)

  domain Python;

  rule revise_arguments(f:IDENTIFIER,A:expression,B:expression,
                                     C:expression,D:expression):primary->primary
  =  " \f(\A,(\B),(\C),(\D)) "
  -> " \f(\C,(\B),(\D)) ";

上面的符号是DMS规则重写语言(RSL)。 “......”是从Python RSL语言中分离Python语法(在那些引号内,DMS因为域符号声明而知道它是Python)的元引用。元引用内的\ n引用规则参数列表中定义的命名非终结类型的语法变量占位符。是的,元数引用中的(...)是Python()......就DMS而言,它们存在于语法树中,因为它们与语言的其余部分一样,只是语法

上面的规则看起来有点奇怪,因为我试图尽可能地跟随你的例子,从表达语言的角度来看,你的例子很奇怪,因为它确实有不寻常的括号。

使用此规则,DMS可以解析Python(使用其Python解析器),如

        foobar(2+3,(x-y),(p),(baz()))

构建一个AST,将(解析为AST)规则与该AST匹配,将其重写为另一个AST,对应于:

        foobar(p,(x-y),(baz()))

然后将表面语法(有效)python打印出来。

如果您希望您的示例是LISP代码的转换,那么您就是 需要一个用于DMS的LISP语法(不难构建,但我们没有太多 调用此方法),并编写相应的表面语法:

 domain Lisp;

  rule revise_form(A:form,B:form, C:form, D:form):form->form
  =  " (\A,(\B),(\C),(\D)) "
  -> " (\C,(\B),(\D)) ";

通过查看Algebra as a DMS domain,您可以更好地感受到这一点。

如果您的目标是在Python中实现所有这些...我没有太多帮助。 DMS是一个相当大的系统,复制需要付出很多努力。