最近在研究抽象语法树的编码,需要使用Python的Javalang库解析Java源代码为抽象语法树,记录一波该库的一些用法。
这里javalang的版本选择最新的0.13.0,老版本的0.11.0显示信息不全,不便于学习。
以最简单的代码为例,学习javalang的结构:
package fuck;
import com.sta.aaa;
import com.sta.bbb;
public class Main {
private String s;
}
代码仅包含一个类,且类中仅包含一个变量声明。使用Python的javalang库来解析它:
import javalang
fd = open("C:/Users/root/Desktop/java.java", "r", encoding="utf-8") #读取Java源代码
tree = javalang.parse.parse(fd.read()) # 根据源代码解析出一颗抽象语法树
print(tree)
效果:
# 打印整颗语法树:CompilationUnit(imports=[Import(path=com.sta.aaa, static=False, wildcard=False), Import(path=com.sta.bbb, static=False, wildcard=False)], package=PackageDeclaration(annotations=None, documentation=None, modifiers=None, name=fuck), types=[ClassDeclaration(annotations=[], body=[FieldDeclaration(annotations=[], declarators=[VariableDeclarator(dimensions=[], initializer=None, name=s)], documentation=None, modifiers={'private'}, type=ReferenceType(arguments=None, dimensions=[], name=String, sub_type=None))], documentation=None, extends=None, implements=None, modifiers={'public'}, name=Main, type_parameters=None)])
这里返回了一个javalang.tree.CompilationUnit类型,表示整颗抽象语法树(以后简称AST)
在print(tree)后面添加以下代码,打印该编译单元的各个子节点:
for i in range(0,len(tree.children)):
print(tree.children[i])
效果:
children[0]:PackageDeclaration(annotations=None, documentation=None, modifiers=None, name=fuck)
chileren[1]:[Import(path=com.sta.aaa, static=False, wildcard=False), Import(path=com.sta.bbb, static=False, wildcard=False)]
children[2]:[ClassDeclaration(annotations=[], body=[FieldDeclaration(annotations=[], declarators=[VariableDeclarator(dimensions=[], initializer=None, name=s)], documentation=None, modifiers={'private'}, type=ReferenceType(arguments=None, dimensions=[], name=String, sub_type=None))], documentation=None, extends=None, implements=None, modifiers={'public'}, name=Main, type_parameters=None)]
可以看到:CompilationUnit(编译单元)的children是一个数组,由三个元素构成:[包声明,Import声明数组,类声明]
值得注意的是,如果源代码中不存在包声明、Import声明、类声明,相应的children[0]、children[1]、children[2]只会被置为“[]”,依然要占位置,也就是说CompilationUnit。children的结构是固定的。
包声明和Import声明数组没什么好说的,接下来要研究ClassDeclaration。
为了简化问题,再次把Java源代码精简一下:
package fuck;
import com.sta.aaa;
import com.sta.bbb;
public class Main {
}
只包含一个类,且该类是空的。
得到的类声明如下:
ClassDeclaration(annotations=[], body=[], documentation=None, extends=None, implements=None, modifiers={'public'}, name=Main, type_parameters=None)
这里annotations表示注释,body表示类括号里面的内容,我们主要看body里面的内容。
这里空类不行了,要在里面加点内容:
package fuck;
import com.sta.aaa;
import com.sta.bbb;
public class Main {
private String s;
public static void main(String[] args) {
System.out.println();
}
}
然后打印body里面的内容:print(tree.children[2][0].body)
[FieldDeclaration(annotations=[], declarators=[VariableDeclarator(dimensions=[], initializer=None, name=s)], documentation=None, modifiers={'private'}, type=ReferenceType(arguments=None, dimensions=[], name=String, sub_type=None)), MethodDeclaration(annotations=[], body=[StatementExpression(expression=MethodInvocation(arguments=[], member=println, postfix_operators=[], prefix_operators=[], qualifier=System.out, selectors=[], type_arguments=None), label=None)], documentation=None, modifiers={'static', 'public'}, name=main, parameters=[FormalParameter(annotations=[], modifiers=set(), name=args, type=ReferenceType(arguments=None, dimensions=[None], name=String, sub_type=None), varargs=False)], return_type=None, throws=None, type_parameters=None)]
这里打印body的type可以知道body也是一个数组,包含不同的声明,可以有FieldDeclaration、MethodDeclaration、LocalVariableDeclaration等,这里声明了一个变量,一个函数,所以body的长度就是2,如果只有一个变量被声明,那么body的长度就是1,此外,注释不算在body里面。
变量声明FieldDeclaration放到后面再说,按照编译单元-----类声明------函数声明的顺序,从大到小来解析。下面到函数声MethodDeclaration
Java里面函数也叫方法,是面向对象的称呼。
这里源码的函数声明也很简单:
打印函数声明看看:
MethodDeclaration(annotations=[], body=[StatementExpression(expression=MethodInvocation(arguments=[], member=println, postfix_operators=[], prefix_operators=[], qualifier=System.out, selectors=[], type_arguments=None), label=None)], documentation=None, modifiers={'static', 'public'}, name=main, parameters=[FormalParameter(annotations=[], modifiers=set(), name=args, type=ReferenceType(arguments=None, dimensions=[None], name=String, sub_type=None), varargs=False)], return_type=None, throws=None, type_parameters=None)
函数声明的结构和类声明类似,也是看body,body也是一个数组,根据代码顺序确定里面的元素。
body里面的元素就是表达式语句StatementExpression。