从ANTLR解析树生成Python AST?
我找到了一份ANTLRv4 的 Python3 语法,但是它生成的解析树里有很多没用的节点。
我在寻找一个可以从这个解析树中得到 Python 抽象语法树(AST)的现成工具包。
有没有这样的东西呢?
补充说明:关于使用 Python 的 ast
包,我的项目是用 Java 写的,我需要解析 Python 文件。
补充说明 2:我所说的 'AST' 是指http://docs.python.org/2/library/ast.html#abstract-grammar,而 '解析树' 是指http://docs.python.org/2/reference/grammar.html。
4 个回答
ANTLR4可以生成一个访问者,这个访问者可以用来遍历解析树,并构建抽象语法树(AST)。如果你使用的是Python,Python有一个叫做ast
的包,所以这应该不是问题。
我用ANTLR4写了一个简单的Python解释器,这是我学习的一部分,使用的是Python 3。访问者的代码在/tinypy/AST/builder/
目录下,你可以看看是怎么实现的。
我找到了一种解决方法:
可以使用 Jython
和 ast
(感谢 @delnan 指引我找到这个)。或者,你也可以直接在 Python 代码中完成所有需要的操作,然后把结果返回给 Java。
PythonInterpreter interpreter = new PythonInterpreter();
interpreter.exec("import ast");
PyObject o = interpreter.eval(
"ast.dump(ast.parse('f(arg1=\\'1\\')', 'filename', 'eval'))" + "\n");
System.out.print(o.toString());
输出结果是
Expression(body=Call(func=Name(id='f', ctx=Load()), args=[], keywords=[keyword(arg='arg1', value=Str(s='1'))], starargs=None, kwargs=None))
这个方法并没有严格回答问题,也可能不适合所有用户,所以我把这个答案留作未选中状态。
Eclipse DLTK项目中的Python子项目用Java实现了一个自定义的Python抽象语法树(AST)模型。这个模型是基于AntlrV3的抽象语法树构建的,但将其改造成基于AntlrV4的解析树应该不会太难。
Eclipse的PyDev项目也可能实现了一个基于Java的Python源代码的抽象语法树。需要注意的是,这两个项目的源代码结构应该非常相似。
当然,在使用这些源代码之前,最好先检查一下许可证,以确保没有问题。
下面的内容可以作为一个开始:
public class AST {
private final Object payload;
private final List<AST> children;
public AST(ParseTree tree) {
this(null, tree);
}
private AST(AST ast, ParseTree tree) {
this(ast, tree, new ArrayList<AST>());
}
private AST(AST parent, ParseTree tree, List<AST> children) {
this.payload = getPayload(tree);
this.children = children;
if (parent == null) {
walk(tree, this);
}
else {
parent.children.add(this);
}
}
public Object getPayload() {
return payload;
}
public List<AST> getChildren() {
return new ArrayList<>(children);
}
private Object getPayload(ParseTree tree) {
if (tree.getChildCount() == 0) {
return tree.getPayload();
}
else {
String ruleName = tree.getClass().getSimpleName().replace("Context", "");
return Character.toLowerCase(ruleName.charAt(0)) + ruleName.substring(1);
}
}
private static void walk(ParseTree tree, AST ast) {
if (tree.getChildCount() == 0) {
new AST(ast, tree);
}
else if (tree.getChildCount() == 1) {
walk(tree.getChild(0), ast);
}
else if (tree.getChildCount() > 1) {
for (int i = 0; i < tree.getChildCount(); i++) {
AST temp = new AST(ast, tree.getChild(i));
if (!(temp.payload instanceof Token)) {
walk(tree.getChild(i), temp);
}
}
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
AST ast = this;
List<AST> firstStack = new ArrayList<>();
firstStack.add(ast);
List<List<AST>> childListStack = new ArrayList<>();
childListStack.add(firstStack);
while (!childListStack.isEmpty()) {
List<AST> childStack = childListStack.get(childListStack.size() - 1);
if (childStack.isEmpty()) {
childListStack.remove(childListStack.size() - 1);
}
else {
ast = childStack.remove(0);
String caption;
if (ast.payload instanceof Token) {
Token token = (Token) ast.payload;
caption = String.format("TOKEN[type: %s, text: %s]",
token.getType(), token.getText().replace("\n", "\\n"));
}
else {
caption = String.valueOf(ast.payload);
}
String indent = "";
for (int i = 0; i < childListStack.size() - 1; i++) {
indent += (childListStack.get(i).size() > 0) ? "| " : " ";
}
builder.append(indent)
.append(childStack.isEmpty() ? "'- " : "|- ")
.append(caption)
.append("\n");
if (ast.children.size() > 0) {
List<AST> children = new ArrayList<>();
for (int i = 0; i < ast.children.size(); i++) {
children.add(ast.children.get(i));
}
childListStack.add(children);
}
}
}
return builder.toString();
}
}
这个代码可以用来为输入的内容 "f(arg1='1')\n"
创建一个抽象语法树(AST),具体如下:
public static void main(String[] args) {
Python3Lexer lexer = new Python3Lexer(new ANTLRInputStream("f(arg1='1')\n"));
Python3Parser parser = new Python3Parser(new CommonTokenStream(lexer));
ParseTree tree = parser.file_input();
AST ast = new AST(tree);
System.out.println(ast);
}
运行后会输出:
'- file_input |- stmt | |- small_stmt | | |- atom | | | '- TOKEN[type: 35, text: f] | | '- trailer | | |- TOKEN[type: 47, text: (] | | |- arglist | | | |- test | | | | '- TOKEN[type: 35, text: arg1] | | | |- TOKEN[type: 53, text: =] | | | '- test | | | '- TOKEN[type: 36, text: '1'] | | '- TOKEN[type: 48, text: )] | '- TOKEN[type: 34, text: \n] '- TOKEN[type: -1, text: ]
我知道这里面可能还有一些你不想要的节点,但你可以添加一组想要排除的标记类型。随意修改吧!
这里有一个Gist,里面包含了上面代码的一个版本,带有正确的导入语句和一些JavaDocs以及内联注释。