groovy负责词法、语法分析groovy文件,然后用asm生成普通的class文件,供jvm使用
这回稍许详细的分析一下源码。
我这里还是选用的最早期的groovy的初版。
整理好的代码可在附件下载,经过改装,可在maven2, maven3下编译通过,并导入到eclipse中。我这里就叫他为groovy0.1吧。
主程序在Compiler里,编译流程为3阶段。stageOne,stageTwo,stageThree。
1.stageOneCompile
代码都在以下3个包里:
- org.codehaus.groovy.syntax
- org.codehaus.groovy.syntax.lexer 词法分析
- org.codehaus.groovy.syntax.parser 语法分析
Compiler.java
protected CSTNode stageOneCompile(File file) throws Exception { LOG.info( "stage-1 compiling: " + file ); FileInputStream fileIn = new FileInputStream( file ); BufferedInputStream bufferedIn = new BufferedInputStream( fileIn ); InputStreamCharStream charStream = new InputStreamCharStream( bufferedIn ); try { Lexer lexer = new Lexer( charStream ); Parser parser = new Parser( new LexerTokenStream( lexer ) ); return parser.compilationUnit(); } finally { charStream.close(); } }
大致就是Lexer负责词法分析,Parser负责语法分析,生成CST。
1.1 词法分析
也就是拆词,或者有点lucene里的分词的意思。
比如 "package cheese.toast"
Lexer.nextToken每次调用返回的Token依次是
package
cheese
.
toast
1.2 语法分析。生成具体语法树(CST)
可参考ParserTest.testPackageDeclaration_OneDot()
比如 "package abc.def.ghi"
调用以下代码,
CSTNode root = parser.packageDeclaration();
解析以后变成一棵树,
package
|--- .(dot)
|--- .(dot)
| |----abc
| |----def
|----ghi
以上2步都属于Compiler.stageOneCompile
stageOneCompile会去调用Parser.compilationUnit(),得到一个解析好的语法树。
2.stageTwoCompile
语义合法验证,内容暂时是空的,先略过。
3. stageThreeCompile
这里除了ASTBuilder类在org.codehaus.groovy.syntax.parser以外,
还有代码集中在org.codehaus.groovy.ast包中。
Compiler.java
protected void stageThreeCompile(CSTNode compilationUnit, File file ) throws Exception { ASTBuilder astBuilder = new ASTBuilder( getClassLoader() ); ClassNode[] classNodes = astBuilder.build( compilationUnit ); for ( int i = 0 ; i < classNodes.length ; ++i ) { dumpClass( classNodes[ i ], file ); } }
可以看到2个步骤,先build AST,然后dump class。
3.1 生成抽象语法树(AST)
ASTBuilder.build()
关于CST和AST的区别可以参考
http://eli.thegreenplace.net/2009/02/16/abstract-vs-concrete-syntax-trees
简单来说,AST就是将CST简化了,去掉以及合并了很多没用的节点,比如去掉分号啦,合并abc.def.ghi这种带有好多点的包名等等。
比如包名,CST转成AST以后,大致像下面这般模样:
package
|--- abc.def.ghi
如图,生成的抽象语法树有如下这些节点,所有的节点都是继承自ASTNode。
比如最基本的,一个类的父亲节点是ClassNode,它的儿子有FieldNode和MethodNode分别代表属性和方法。
3.1.1 getter/setter
我们研究下groovy在哪里自动生成getter/setter的。
查看代码ClassNode.addProperty()
public void addProperty(PropertyNode node) { FieldNode field = new FieldNode(node.getName(), ACC_PRIVATE, node.getType(), getName(), node.getInitialValueExpression()); addField(field); String name = node.getName(); String getterName = "get" + capitalize(name); String setterName = "set" + capitalize(name); Statement getterBlock = node.getGetterBlock(); if (getterBlock == null) { getterBlock = createGetterBlock(node, field); } Statement setterBlock = node.getGetterBlock(); if (setterBlock == null) { setterBlock = createSetterBlock(node, field); } MethodNode getter = new MethodNode( getterName, node.getModifiers(), node.getType(), Parameter.EMPTY_ARRAY, getterBlock); addMethod(getter); Parameter[] setterParameterTypes = { new Parameter(node.getType(), "value")}; MethodNode setter = new MethodNode(setterName, node.getModifiers(), "void", setterParameterTypes, setterBlock); addMethod(setter); properties.add(node); }
可以发现在增加属性节点的同时,就给自动生成了2个方法节点,getter和setter。
3.2 dumpClass
这里既是用ASM生成java字节码了,毋庸置疑,用到了有名的visitor模式。
protected void dumpClass(ClassNode classNode, File file) throws Exception { ClassWriter classWriter = new ClassWriter( true ); ClassGenerator classGenerator = new ClassGenerator( classWriter, getClassLoader(), file.getName() ); classGenerator.visitClass( classNode ); byte[] code = classWriter.toByteArray(); File outputFile = createOutputFile( classNode.getName() ); if ( ! outputFile.getParentFile().exists() ) { outputFile.getParentFile().mkdirs(); } LOG.info( "generating class to: " + outputFile ); FileOutputStream out = new FileOutputStream( outputFile ); try { out.write( code ); } finally { out.close(); } }
ASM和visitor模式暂时不展开了。后面有机会再深入分析。