Groovy实现原理分析——语法分析
在上一篇文章中我们分析了Groovy的词法分析过程,了解了GroovyLexer.g4如何将源代码字符流转换为Token流。本篇我们继续深入,看看Groovy是如何将Token流转换为AST(Abstract Syntax Tree,抽象语法树)的。
语法分析的两个阶段
在Groovy 3.0中,语法分析实际上分为两个编译阶段:
PARSING阶段:由Antlr4的Parser将Token流解析为CST(Concrete Syntax Tree,具体语法树,也叫Parse Tree)CONVERSION阶段:由AstBuilder将CST转换为Groovy的AST
这两个阶段在Antlr4ParserPlugin中被统一处理。
GroovyParser.g4——语法规则定义
Groovy的语法规则定义在src/antlr/GroovyParser.g4文件中:
parser grammar GroovyParser;
options {
tokenVocab = GroovyLexer;
contextSuperClass = GroovyParserRuleContext;
superClass = AbstractParser;
}
这里有几个要点:
tokenVocab = GroovyLexer:指定Token词汇表来自GroovyLexer,将词法分析器和语法分析器关联起来contextSuperClass = GroovyParserRuleContext:自定义的规则上下文基类,实现了NodeMetaDataHandler接口,可以在Parse Tree节点上附加元数据superClass = AbstractParser:自定义的Parser基类
编译单元规则
语法分析的起始规则是compilationUnit:
compilationUnit
: nls (packageDeclaration sep?)? scriptStatements? EOF
;
scriptStatements
: scriptStatement (sep scriptStatement)* sep?
;
scriptStatement
: importDeclaration
| typeDeclaration
| { !SemanticPredicates.isInvalidMethodDeclaration(_input) }?
methodDeclaration[3, 9]
| statement
;
一个Groovy编译单元由可选的包声明和一系列脚本语句组成。脚本语句可以是import声明、类型声明、方法声明或普通语句。
注意methodDeclaration前面的语义谓词{ !SemanticPredicates.isInvalidMethodDeclaration(_input) }?,这是Antlr4的语义谓词机制,用于在语法分析阶段进行额外的语义检查。
类声明规则
typeDeclaration
: classOrInterfaceModifiersOpt classDeclaration
;
classOrInterfaceModifier
: annotation
| m=( PUBLIC
| PROTECTED
| PRIVATE
| STATIC
| ABSTRACT
| FINAL
| STRICTFP
| DEFAULT
)
;
修饰符规则
Groovy的修饰符系统比Java更灵活,支持def和var关键字:
modifier
: classOrInterfaceModifier
| m=( NATIVE
| SYNCHRONIZED
| TRANSIENT
| VOLATILE
| DEF
| VAR
)
;
variableModifier
: annotation
| m=( FINAL | DEF | VAR | PUBLIC | PROTECTED | PRIVATE | STATIC | ABSTRACT | STRICTFP )
;
CST的构建
CST的构建发生在AstBuilder.buildCST()方法中:
private GroovyParserRuleContext buildCST() throws CompilationFailedException {
GroovyParserRuleContext result;
try {
AtnManager.READ_LOCK.lock();
try {
result = buildCST(PredictionMode.SLL);
} catch (Throwable t) {
if (t instanceof GroovySyntaxError
&& GroovySyntaxError.LEXER == ((GroovySyntaxError) t).getSource()) {
throw t;
}
result = buildCST(PredictionMode.LL);
} finally {
AtnManager.READ_LOCK.unlock();
}
} catch (Throwable t) {
throw convertException(t);
}
return result;
}
这里使用了Antlr4的两阶段解析策略:
- 首先尝试
SLL(Strong LL)模式,这是一种更快但不够强大的解析模式 - 如果
SLL模式失败(遇到歧义),则回退到完整的LL模式
这种策略在大多数情况下能获得更好的性能,因为大部分Groovy代码不需要完整的LL解析能力。
private GroovyParserRuleContext buildCST(PredictionMode predictionMode) {
parser.getInterpreter().setPredictionMode(predictionMode);
if (PredictionMode.SLL.equals(predictionMode)) {
this.removeErrorListeners();
} else {
parser.getInputStream().seek(0);
this.addErrorListeners();
}
return parser.compilationUnit();
}
parser.compilationUnit()调用的就是GroovyParser.g4中定义的compilationUnit规则,Antlr4会自动生成对应的解析方法。
CST到AST的转换——AstBuilder
AstBuilder是Groovy 3.0中最核心的类之一,它继承自Antlr4生成的GroovyParserBaseVisitor,通过Visitor模式遍历CST,将其转换为Groovy的AST。
public class AstBuilder extends GroovyParserBaseVisitor<Object> {
private final SourceUnit sourceUnit;
private final ModuleNode moduleNode;
private final GroovyLangLexer lexer;
private final GroovyLangParser parser;
// ...
}
buildAST方法
public ModuleNode buildAST() {
try {
return (ModuleNode) this.visit(this.buildCST());
} catch (Throwable t) {
throw convertException(t);
}
}
buildAST方法先调用buildCST()构建CST,然后通过this.visit()遍历CST,触发各个visitXxx方法,最终生成ModuleNode。
visitCompilationUnit
@Override
public ModuleNode visitCompilationUnit(CompilationUnitContext ctx) {
this.visit(ctx.packageDeclaration());
for (ASTNode node : this.visitScriptStatements(ctx.scriptStatements())) {
if (node instanceof DeclarationListStatement) {
for (Statement stmt: ((DeclarationListStatement) node).getDeclarationStatements()) {
this.moduleNode.addStatement(stmt);
}
} else if (node instanceof Statement) {
this.moduleNode.addStatement((Statement) node);
} else if (node instanceof MethodNode) {
this.moduleNode.addMethod((MethodNode) node);
}
}
for (ClassNode node : this.classNodeList) {
this.moduleNode.addClass(node);
}
this.configureScriptClassNode();
return this.moduleNode;
}
这个方法展示了CST到AST转换的核心逻辑:
- 处理包声明
- 遍历所有脚本语句,根据类型分别添加到
ModuleNode中 - 添加所有类定义
- 配置脚本类节点
visitImportDeclaration
以import声明为例,看看一个具体的visit方法是如何工作的:
@Override
public ImportNode visitImportDeclaration(ImportDeclarationContext ctx) {
ImportNode importNode;
boolean hasStatic = asBoolean(ctx.STATIC());
boolean hasStar = asBoolean(ctx.MUL());
boolean hasAlias = asBoolean(ctx.alias);
List<AnnotationNode> annotationNodeList = this.visitAnnotationsOpt(ctx.annotationsOpt());
// ... 根据不同的import形式创建对应的ImportNode
}
每个visitXxx方法都接收对应的CST上下文节点,从中提取信息,创建对应的AST节点。
AST的核心节点类型
Groovy的AST由org.codehaus.groovy.ast包中的类组成,核心节点类型包括:
ModuleNode:模块节点,代表一个源文件,是AST的根节点ClassNode:类节点,代表一个类定义MethodNode:方法节点,代表一个方法定义FieldNode:字段节点PropertyNode:属性节点Parameter:参数节点
表达式节点(org.codehaus.groovy.ast.expr包):
VariableExpression:变量表达式ConstantExpression:常量表达式BinaryExpression:二元表达式MethodCallExpression:方法调用表达式ClosureExpression:闭包表达式LambdaExpression:Lambda表达式(Groovy 3.0新增)
语句节点(org.codehaus.groovy.ast.stmt包):
BlockStatement:代码块ExpressionStatement:表达式语句IfStatement:if语句ForStatement:for语句ReturnStatement:return语句TryCatchStatement:try-catch语句
CONVERSION阶段
在CompilationUnit中,CONVERSION阶段的操作定义如下:
addPhaseOperation(source -> {
source.convert();
getAST().addModule(source.getAST());
Optional.ofNullable(getProgressCallback())
.ifPresent(callback -> callback.call(source, getPhase()));
}, Phases.CONVERSION);
source.convert()最终调用SourceUnit.buildAST():
public ModuleNode buildAST() {
if (null != this.ast) {
return this.ast;
}
try {
this.ast = parserPlugin.buildAST(this, this.classLoader, this.cst);
this.ast.setDescription(this.name);
} catch (SyntaxException e) {
if (this.ast == null) {
this.ast = new ModuleNode(this);
}
getErrorCollector().addError(new SyntaxErrorMessage(e, this));
}
return this.ast;
}
这里parserPlugin.buildAST()就是Antlr4ParserPlugin.buildAST(),它创建AstBuilder并调用buildAST()完成CST到AST的转换。
SEMANTIC_ANALYSIS阶段
AST构建完成后,进入语义分析阶段。这个阶段主要做以下工作:
变量作用域分析
private final ISourceUnitOperation resolve = (final SourceUnit source) -> {
for (ClassNode classNode : source.getAST().getClasses()) {
GroovyClassVisitor visitor = new VariableScopeVisitor(source);
visitor.visitClass(classNode);
resolveVisitor.setClassNodeResolver(classNodeResolver);
resolveVisitor.startResolving(classNode, source);
}
};
VariableScopeVisitor负责分析变量的作用域,ResolveVisitor负责解析类引用。
ResolveVisitor
ResolveVisitor是语义分析阶段最重要的Visitor之一,它负责将AST中的类名引用解析为实际的ClassNode:
public class ResolveVisitor extends ClassCodeExpressionTransformer {
public static final String[] DEFAULT_IMPORTS = {
"java.lang.", "java.util.", "java.io.", "java.net.",
"groovy.lang.", "groovy.util."
};
// ...
}
注意Groovy默认导入了6个包,这就是为什么在Groovy中可以直接使用List、Map等类而不需要显式import。
其他语义分析操作
CompilationUnit在SEMANTIC_ANALYSIS阶段还注册了多个操作:
StaticImportVisitor:处理静态导入InnerClassVisitor:处理内部类GenericsVisitor:检查泛型的正确性StaticVerifier:验证静态成员的使用ClassCompletionVerifier:验证类定义的完整性
一个例子
以我们在第一篇中的示例代码为例:
package edu.jiangxin.test
class Test {
static void main(def args) {
def mygreeting = "Hello World"
println mygreeting
}
}
经过词法分析后,生成的Token流大致如下:
PACKAGE, "edu", DOT, "jiangxin", DOT, "test", NL,
CLASS, "Test", LBRACE, NL,
STATIC, VOID, "main", LPAREN, DEF, "args", RPAREN, LBRACE, NL,
DEF, "mygreeting", ASSIGN, StringLiteral("Hello World"), NL,
"println", "mygreeting", NL,
RBRACE, NL,
RBRACE, EOF
经过语法分析后,生成的AST结构大致如下:
ModuleNode
└── ClassNode (Test)
└── MethodNode (main)
└── BlockStatement
├── ExpressionStatement
│ └── DeclarationExpression
│ ├── VariableExpression (mygreeting)
│ └── ConstantExpression ("Hello World")
└── ExpressionStatement
└── MethodCallExpression
├── VariableExpression (this)
├── ConstantExpression ("println")
└── ArgumentListExpression
└── VariableExpression (mygreeting)
小结
Groovy 3.0的语法分析基于Antlr4实现,核心文件是GroovyParser.g4和AstBuilder.java。语法分析分为两步:首先由Antlr4的Parser将Token流解析为CST,然后由AstBuilder通过Visitor模式将CST转换为Groovy的AST。语义分析阶段则通过多个Visitor对AST进行类型解析、作用域分析等处理。
下一篇我们将分析Groovy如何利用ASM将AST转换为JVM字节码。
(未完待续)
Comments