【问题标题】:How to remove AST nodes with pycparser?如何使用 pycparser 删除 AST 节点?
【发布时间】:2020-02-12 19:41:48
【问题描述】:

让我们从考虑这个sn-p开始:

import sys

from pycparser import c_parser, c_ast, c_generator


text = r"""
void main() {
    foo(1,3);

    foo1(4);

    x = 1;

     foo2(4,

        10,


        3);
    foo3(
        "xxx"

    );
}
"""


class FuncCallVisitor(c_ast.NodeVisitor):

    def visit_FuncCall(self, node):
        print('%s called at %s' % (node.name.name, node.name.coord))

        if node.args:
            self.visit(node.args)


class RemoveFuncCalls(c_generator.CGenerator):

    def visit_FuncCall(self, n):
        # fref = self._parenthesize_unless_simple(n.name)
        # return fref + '(' + self.visit(n.args) + ')'
        return ""


if __name__ == '__main__':
    parser = c_parser.CParser()
    ast = parser.parse(text)
    v = FuncCallVisitor()
    v.visit(ast)
    print('-' * 80)

    ast.show(showcoord=True)
    generator = RemoveFuncCalls()

    print('-' * 80)
    print(generator.visit(ast))

上面的输出将是:

void main()
{
  ;
  ;
  x = 1;
  ;
  ;
}

但我希望它变成这样:

void main()
{
  x = 1;
}

所以我的问题是,使用 pycparser 从 AST 中删除节点/子树的规范/惯用方法是什么?

【问题讨论】:

    标签: python abstract-syntax-tree pycparser


    【解决方案1】:

    看起来c_generator.CGenerator 调用_generate_stmt method 用于类似范围的结构which appends ';\n'(带有缩进)到visit for 语句的结果,即使它是一个空字符串。

    要删除函数调用,我们可以像这样重载它

    class RemoveFuncCalls(c_generator.CGenerator):
        def _generate_stmt(self, n, add_indent=False):
            if isinstance(n, c_ast.FuncCall):
                return ''
            else:
                return super()._generate_stmt(n, add_indent)
    

    这样

    void main()
    {
      x = 1;
    }
    

    看起来像你想要的。

    让我们考虑一个案例

    if (bar(42, "something"))
        return;
    

    如果我们需要它变成

    if ()
        return;
    

    那么我们需要添加

        def visit_FuncCall(self, n):
            return ''
    

    就像在 OP 中一样,因为 RemoveFuncCalls.visit_If 方法不会调用 _generate_stmt 来进行 cond 字段序列化。

    走得更远

    我不知道“使用 pycparser 从 AST 中删除节点/子树的规范/惯用方法”,但我知道 stdlib 中 ast 模块的一种方法——ast.NodeTransformer class(@987654338 中没有@ 出于某种原因)。

    这将允许我们通过重写 private'ish 方法并修改 AST 本身来避免弄乱 AST 如何序列化为str

    from pycparser import c_ast
    
    class NodeTransformer(c_ast.NodeVisitor):
        def generic_visit(self, node):
            for field, old_value in iter_fields(node):
                if isinstance(old_value, list):
                    new_values = []
                    for value in old_value:
                        if isinstance(value, c_ast.Node):
                            value = self.visit(value)
                            if value is None:
                                continue
                            elif not isinstance(value, c_ast.Node):
                                new_values.extend(value)
                                continue
                        new_values.append(value)
                    old_value[:] = new_values
                elif isinstance(old_value, c_ast.Node):
                    new_node = self.visit(old_value)
                    setattr(node, field, new_node)
            return node
    
    
    def iter_fields(node):
        # this doesn't look pretty because `pycparser` decided to have structure 
        # for AST node classes different from stdlib ones
        index = 0
        children = node.children()
        while index < len(children):
            name, child = children[index]
            try:
                bracket_index = name.index('[')
            except ValueError:
                yield name, child
                index += 1
            else:
                name = name[:bracket_index]
                child = getattr(node, name)
                index += len(child)
                yield name, child
    

    对于我们的例子,它可以简单地被子类化

    class FuncCallsRemover(NodeTransformer):
        def visit_FuncCall(self, node):
            return None
    

    和使用一样

    ...
    ast = parser.parse(text)
    v = FuncCallsRemover()
    ast = v.visit(ast)  # note that `NodeTransformer` returns modified AST instead of `None`
    

    之后我们可以使用未修改的c_generator.CGenerator 实例并获得相同的结果。

    【讨论】:

      猜你喜欢
      • 2019-07-10
      • 1970-01-01
      • 1970-01-01
      • 2010-12-15
      • 2017-05-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多