Kotlin 编译之路 "Kotlin编译器"

Kotlin 编译之路 "Kotlin编译器"

背景

阅读这边文章之前建议首先阅读下面的文章

小萝卜:Kotlin 编译之路 "JAVA编译器"zhuanlan.zhihu.com图标

Kotlin作为一门新语言,它的一切都那么似曾相识,它和JAVA一样都是基于JVM的,它是通过Kotlin编译器生成的JVM字节码运行在JVM上,因此它和JAVA是完全兼容的,当然他们的编译过程也是及其相似。

“源码之下-了无秘密“ Kotlin源码地址如下

JetBrains/kotlingithub.com图标

入口函数

项目clone下来除去不必要文件足足有130M之多,展开文件,大多数都是JAVA和KT文件,该如何下手呢?

其实看源码多了,你就习以为常了,入口函数其实很容易找,最简单的办法,就是整个工程搜索main函数,如果搜不到业可以尝试doMain函数,当然搜出来可能会很多,那怎么办,其实业不难,就在源码中寻找compiler模块就行了,问我为什么?请自己翻阅一下编译原理,那么Kotlin该如何入手呢?

其实上图有两个核心信息,kotlinc 好熟悉的感觉,是不是觉得像 javac 啊!看kotlinc指令最终的真实指向kotlin-compiler.jar,那么太好了,好像找到了突破口。

终于找到了我们想要的(上图最下方红色圈选区)

manifest.attributes["Main-Class"] = "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler"

我们在IDE中全局查找一下K2JVMCompiler.kt文件,如下

如此找到了红色区域,就是我们要的编译入口:

companion object {
@JvmStatic
fun main(args: Array<String>) {
    CLITool.doMain(K2JVMCompiler(), args)
    }
}

fun main(args: Array<String>) = K2JVMCompiler.main(args)

可以看出编译的核心入口代码

 compiler.exec(System.err, *args)

在CLITool中继续翻阅,跳转

fun compileBunchOfSources(environment: KotlinCoreEnvironment): Boolean {
    val moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(environment.project)
    val friendPaths = environment.configuration.getList(JVMConfigurationKeys.FRIEND_PATHS)
    for (path in friendPaths) {
        moduleVisibilityManager.addFriendPath(path)
    }
    if (!checkKotlinPackageUsage(environment, environment.getSourceFiles())) return false
    val generationState = analyzeAndGenerate(environment) ?: return false
    try {
        writeOutput(environment.configuration, generationState.factory, MainClassProvider(generationState, environment))
        return true
    } finally {
        generationState.destroy()
    }
}

我们看到我们最主要的一个方法

val generationState = analyzeAndGenerate(environment) ?: return false

继续跳进去你会法线其实这就是编译原理里面那些词法、语法、语义分析和目标代码生成的整个过程

编译

从上面源码中获得信息可知 Kotlin 的整个编译过程和 Java 或者其它语言没有什么不同

1. 词法分析 
2. 语法分析
3. 语义分析及中间代码生成 
4. 目标代码生成

其中,我们把词法分析、语法分析、语义分析及中间代码生成称之为编译器前段,将源程序翻译成中间代码;目标代码生成称之为编译器后端,负责将中间代码转换生成目标代码,与目标语言有关的细节尽可能放在了后端。

词法分析

其实词法分析就是将源程序读入的字符序列,按照一定的规则转换成词法单元(Token)序列的过程。词法单元是语言中具有独立意义的最小单元,包括关键字、标识符、常数、运算符、界符等等。

寻找办法其实很简单,只需要在源码路径中搜索lexer就行,找到如下文件 KtTokens.java 所在路径:kotlin/compiler/psi/src/org/jetbrains/kotlin/lexer/ 下,我们可以打开看一下文件内容如下:

/*
 * Copyright 2010-2015 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jetbrains.kotlin.lexer;

import com.intellij.psi.TokenType;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens;
import org.jetbrains.kotlin.psi.KtPsiUtil;

public interface KtTokens {
 KtToken EOF = new KtToken("EOF");

 KtToken RESERVED = new KtToken("RESERVED");

 KtToken BLOCK_COMMENT = new KtToken("BLOCK_COMMENT");
 KtToken EOL_COMMENT = new KtToken("EOL_COMMENT");
 KtToken SHEBANG_COMMENT = new KtToken("SHEBANG_COMMENT");

 //KtToken DOC_COMMENT   = new KtToken("DOC_COMMENT");
 IElementType DOC_COMMENT = KDocTokens.KDOC;

 IElementType WHITE_SPACE = TokenType.WHITE_SPACE;

 KtToken INTEGER_LITERAL = new KtToken("INTEGER_LITERAL");
 KtToken FLOAT_LITERAL = new KtToken("FLOAT_CONSTANT");
 KtToken CHARACTER_LITERAL = new KtToken("CHARACTER_LITERAL");

 KtToken CLOSING_QUOTE = new KtToken("CLOSING_QUOTE");
 KtToken OPEN_QUOTE = new KtToken("OPEN_QUOTE");
 KtToken REGULAR_STRING_PART = new KtToken("REGULAR_STRING_PART");
 KtToken ESCAPE_SEQUENCE = new KtToken("ESCAPE_SEQUENCE");
 KtToken SHORT_TEMPLATE_ENTRY_START = new KtToken("SHORT_TEMPLATE_ENTRY_START");
 KtToken LONG_TEMPLATE_ENTRY_START = new KtToken("LONG_TEMPLATE_ENTRY_START");
 KtToken LONG_TEMPLATE_ENTRY_END = new KtToken("LONG_TEMPLATE_ENTRY_END");
 KtToken DANGLING_NEWLINE = new KtToken("DANGLING_NEWLINE");

 KtKeywordToken PACKAGE_KEYWORD = KtKeywordToken.keyword("package");
 KtKeywordToken AS_KEYWORD = KtKeywordToken.keyword("as");
 KtKeywordToken TYPE_ALIAS_KEYWORD = KtKeywordToken.keyword("typealias");
 KtKeywordToken CLASS_KEYWORD = KtKeywordToken.keyword("class");
 KtKeywordToken THIS_KEYWORD = KtKeywordToken.keyword("this");
 KtKeywordToken SUPER_KEYWORD = KtKeywordToken.keyword("super");
 KtKeywordToken VAL_KEYWORD = KtKeywordToken.keyword("val");
 KtKeywordToken VAR_KEYWORD = KtKeywordToken.keyword("var");
 KtKeywordToken FUN_KEYWORD = KtKeywordToken.keyword("fun");
 KtKeywordToken FOR_KEYWORD = KtKeywordToken.keyword("for");
 KtKeywordToken NULL_KEYWORD = KtKeywordToken.keyword("null");
 KtKeywordToken TRUE_KEYWORD = KtKeywordToken.keyword("true");
 KtKeywordToken FALSE_KEYWORD = KtKeywordToken.keyword("false");
 KtKeywordToken IS_KEYWORD = KtKeywordToken.keyword("is");
 KtModifierKeywordToken IN_KEYWORD = KtModifierKeywordToken.keywordModifier("in");
 KtKeywordToken THROW_KEYWORD = KtKeywordToken.keyword("throw");
 KtKeywordToken RETURN_KEYWORD = KtKeywordToken.keyword("return");
 KtKeywordToken BREAK_KEYWORD = KtKeywordToken.keyword("break");
 KtKeywordToken CONTINUE_KEYWORD = KtKeywordToken.keyword("continue");
 KtKeywordToken OBJECT_KEYWORD = KtKeywordToken.keyword("object");
 KtKeywordToken IF_KEYWORD = KtKeywordToken.keyword("if");
 KtKeywordToken TRY_KEYWORD = KtKeywordToken.keyword("try");
 KtKeywordToken ELSE_KEYWORD = KtKeywordToken.keyword("else");
 KtKeywordToken WHILE_KEYWORD = KtKeywordToken.keyword("while");
 KtKeywordToken DO_KEYWORD = KtKeywordToken.keyword("do");
 KtKeywordToken WHEN_KEYWORD = KtKeywordToken.keyword("when");
 KtKeywordToken INTERFACE_KEYWORD = KtKeywordToken.keyword("interface");

 // Reserved for future use:
 KtKeywordToken TYPEOF_KEYWORD = KtKeywordToken.keyword("typeof");

 KtToken AS_SAFE = KtKeywordToken.keyword("AS_SAFE");//new KtToken("as?");

 KtToken IDENTIFIER = new KtToken("IDENTIFIER");

 KtToken FIELD_IDENTIFIER = new KtToken("FIELD_IDENTIFIER");
 KtSingleValueToken LBRACKET = new KtSingleValueToken("LBRACKET", "[");
 KtSingleValueToken RBRACKET = new KtSingleValueToken("RBRACKET", "]");
 KtSingleValueToken LBRACE = new KtSingleValueToken("LBRACE", "{");
 KtSingleValueToken RBRACE = new KtSingleValueToken("RBRACE", "}");
 KtSingleValueToken LPAR = new KtSingleValueToken("LPAR", "(");
 KtSingleValueToken RPAR = new KtSingleValueToken("RPAR", ")");
 KtSingleValueToken DOT = new KtSingleValueToken("DOT", ".");
 KtSingleValueToken PLUSPLUS = new KtSingleValueToken("PLUSPLUS", "++");
 KtSingleValueToken MINUSMINUS = new KtSingleValueToken("MINUSMINUS", "--");
 KtSingleValueToken MUL = new KtSingleValueToken("MUL", "*");
 KtSingleValueToken PLUS = new KtSingleValueToken("PLUS", "+");
 KtSingleValueToken MINUS = new KtSingleValueToken("MINUS", "-");
 KtSingleValueToken EXCL = new KtSingleValueToken("EXCL", "!");
 KtSingleValueToken DIV = new KtSingleValueToken("DIV", "/");
 KtSingleValueToken PERC = new KtSingleValueToken("PERC", "%");
 KtSingleValueToken LT = new KtSingleValueToken("LT", "<");
 KtSingleValueToken GT = new KtSingleValueToken("GT", ">");
 KtSingleValueToken LTEQ = new KtSingleValueToken("LTEQ", "<=");
 KtSingleValueToken GTEQ = new KtSingleValueToken("GTEQ", ">=");
 KtSingleValueToken EQEQEQ = new KtSingleValueToken("EQEQEQ", "===");
 KtSingleValueToken ARROW = new KtSingleValueToken("ARROW", "->");
 KtSingleValueToken DOUBLE_ARROW = new KtSingleValueToken("DOUBLE_ARROW", "=>");
 KtSingleValueToken EXCLEQEQEQ = new KtSingleValueToken("EXCLEQEQEQ", "!==");
 KtSingleValueToken EQEQ = new KtSingleValueToken("EQEQ", "==");
 KtSingleValueToken EXCLEQ = new KtSingleValueToken("EXCLEQ", "!=");
 KtSingleValueToken EXCLEXCL = new KtSingleValueToken("EXCLEXCL", "!!");
 KtSingleValueToken ANDAND = new KtSingleValueToken("ANDAND", "&&");
 KtSingleValueToken OROR = new KtSingleValueToken("OROR", "||");
 KtSingleValueToken SAFE_ACCESS = new KtSingleValueToken("SAFE_ACCESS", "?.");
 KtSingleValueToken ELVIS = new KtSingleValueToken("ELVIS", "?:");
 KtSingleValueToken QUEST = new KtSingleValueToken("QUEST", "?");
 KtSingleValueToken COLONCOLON = new KtSingleValueToken("COLONCOLON", "::");
 KtSingleValueToken COLON = new KtSingleValueToken("COLON", ":");
 KtSingleValueToken SEMICOLON = new KtSingleValueToken("SEMICOLON", ";");
 KtSingleValueToken DOUBLE_SEMICOLON = new KtSingleValueToken("DOUBLE_SEMICOLON", ";;");
 KtSingleValueToken RANGE = new KtSingleValueToken("RANGE", "..");
 KtSingleValueToken EQ = new KtSingleValueToken("EQ", "=");
 KtSingleValueToken MULTEQ = new KtSingleValueToken("MULTEQ", "*=");
 KtSingleValueToken DIVEQ = new KtSingleValueToken("DIVEQ", "/=");
 KtSingleValueToken PERCEQ = new KtSingleValueToken("PERCEQ", "%=");
 KtSingleValueToken PLUSEQ = new KtSingleValueToken("PLUSEQ", "+=");
 KtSingleValueToken MINUSEQ = new KtSingleValueToken("MINUSEQ", "-=");
 KtKeywordToken NOT_IN = KtKeywordToken.keyword("NOT_IN", "!in");
 KtKeywordToken NOT_IS = KtKeywordToken.keyword("NOT_IS", "!is");
 KtSingleValueToken HASH = new KtSingleValueToken("HASH", "#");
 KtSingleValueToken AT = new KtSingleValueToken("AT", "@");

 KtSingleValueToken COMMA = new KtSingleValueToken("COMMA", ",");

 KtToken EOL_OR_SEMICOLON = new KtToken("EOL_OR_SEMICOLON");
 KtKeywordToken FILE_KEYWORD = KtKeywordToken.softKeyword("file");
 KtKeywordToken FIELD_KEYWORD = KtKeywordToken.softKeyword("field");
 KtKeywordToken PROPERTY_KEYWORD = KtKeywordToken.softKeyword("property");
 KtKeywordToken RECEIVER_KEYWORD = KtKeywordToken.softKeyword("receiver");
 KtKeywordToken PARAM_KEYWORD = KtKeywordToken.softKeyword("param");
 KtKeywordToken SETPARAM_KEYWORD = KtKeywordToken.softKeyword("setparam");
 KtKeywordToken DELEGATE_KEYWORD = KtKeywordToken.softKeyword("delegate");
 KtKeywordToken IMPORT_KEYWORD = KtKeywordToken.softKeyword("import");
 KtKeywordToken WHERE_KEYWORD = KtKeywordToken.softKeyword("where");
 KtKeywordToken BY_KEYWORD = KtKeywordToken.softKeyword("by");
 KtKeywordToken GET_KEYWORD = KtKeywordToken.softKeyword("get");
 KtKeywordToken SET_KEYWORD = KtKeywordToken.softKeyword("set");
 KtKeywordToken CONSTRUCTOR_KEYWORD = KtKeywordToken.softKeyword("constructor");
 KtKeywordToken INIT_KEYWORD = KtKeywordToken.softKeyword("init");

 KtModifierKeywordToken ABSTRACT_KEYWORD = KtModifierKeywordToken.softKeywordModifier("abstract");
 KtModifierKeywordToken ENUM_KEYWORD = KtModifierKeywordToken.softKeywordModifier("enum");
 KtModifierKeywordToken OPEN_KEYWORD = KtModifierKeywordToken.softKeywordModifier("open");
 KtModifierKeywordToken INNER_KEYWORD = KtModifierKeywordToken.softKeywordModifier("inner");
 KtModifierKeywordToken OVERRIDE_KEYWORD = KtModifierKeywordToken.softKeywordModifier("override");
 KtModifierKeywordToken PRIVATE_KEYWORD = KtModifierKeywordToken.softKeywordModifier("private");
 KtModifierKeywordToken PUBLIC_KEYWORD = KtModifierKeywordToken.softKeywordModifier("public");
 KtModifierKeywordToken INTERNAL_KEYWORD = KtModifierKeywordToken.softKeywordModifier("internal");
 KtModifierKeywordToken PROTECTED_KEYWORD = KtModifierKeywordToken.softKeywordModifier("protected");
 KtKeywordToken CATCH_KEYWORD = KtKeywordToken.softKeyword("catch");
 KtModifierKeywordToken OUT_KEYWORD = KtModifierKeywordToken.softKeywordModifier("out");
 KtModifierKeywordToken VARARG_KEYWORD = KtModifierKeywordToken.softKeywordModifier("vararg");
 KtModifierKeywordToken REIFIED_KEYWORD = KtModifierKeywordToken.softKeywordModifier("reified");
 KtKeywordToken DYNAMIC_KEYWORD = KtKeywordToken.softKeyword("dynamic");
 KtModifierKeywordToken COMPANION_KEYWORD = KtModifierKeywordToken.softKeywordModifier("companion");
 KtModifierKeywordToken SEALED_KEYWORD = KtModifierKeywordToken.softKeywordModifier("sealed");

 KtModifierKeywordToken DEFAULT_VISIBILITY_KEYWORD = PUBLIC_KEYWORD;

 KtKeywordToken FINALLY_KEYWORD = KtKeywordToken.softKeyword("finally");
 KtModifierKeywordToken FINAL_KEYWORD = KtModifierKeywordToken.softKeywordModifier("final");

 KtModifierKeywordToken LATEINIT_KEYWORD = KtModifierKeywordToken.softKeywordModifier("lateinit");

 KtModifierKeywordToken DATA_KEYWORD = KtModifierKeywordToken.softKeywordModifier("data");
 KtModifierKeywordToken INLINE_KEYWORD = KtModifierKeywordToken.softKeywordModifier("inline");
 KtModifierKeywordToken NOINLINE_KEYWORD = KtModifierKeywordToken.softKeywordModifier("noinline");
 KtModifierKeywordToken TAILREC_KEYWORD = KtModifierKeywordToken.softKeywordModifier("tailrec");
 KtModifierKeywordToken EXTERNAL_KEYWORD = KtModifierKeywordToken.softKeywordModifier("external");
 KtModifierKeywordToken ANNOTATION_KEYWORD = KtModifierKeywordToken.softKeywordModifier("annotation");
 KtModifierKeywordToken CROSSINLINE_KEYWORD = KtModifierKeywordToken.softKeywordModifier("crossinline");
 KtModifierKeywordToken OPERATOR_KEYWORD = KtModifierKeywordToken.softKeywordModifier("operator");
 KtModifierKeywordToken INFIX_KEYWORD = KtModifierKeywordToken.softKeywordModifier("infix");

 KtModifierKeywordToken CONST_KEYWORD = KtModifierKeywordToken.softKeywordModifier("const");

 KtModifierKeywordToken SUSPEND_KEYWORD = KtModifierKeywordToken.softKeywordModifier("suspend");

 KtModifierKeywordToken HEADER_KEYWORD = KtModifierKeywordToken.softKeywordModifier("header");
 KtModifierKeywordToken IMPL_KEYWORD = KtModifierKeywordToken.softKeywordModifier("impl");

 KtModifierKeywordToken EXPECT_KEYWORD = KtModifierKeywordToken.softKeywordModifier("expect");
 KtModifierKeywordToken ACTUAL_KEYWORD = KtModifierKeywordToken.softKeywordModifier("actual");

 TokenSet KEYWORDS = TokenSet.create(PACKAGE_KEYWORD, AS_KEYWORD, TYPE_ALIAS_KEYWORD, CLASS_KEYWORD, INTERFACE_KEYWORD,
                                        THIS_KEYWORD, SUPER_KEYWORD, VAL_KEYWORD, VAR_KEYWORD, FUN_KEYWORD, FOR_KEYWORD,
                                        NULL_KEYWORD,
                                        TRUE_KEYWORD, FALSE_KEYWORD, IS_KEYWORD,
                                        IN_KEYWORD, THROW_KEYWORD, RETURN_KEYWORD, BREAK_KEYWORD, CONTINUE_KEYWORD, OBJECT_KEYWORD, IF_KEYWORD,
                                        ELSE_KEYWORD, WHILE_KEYWORD, DO_KEYWORD, TRY_KEYWORD, WHEN_KEYWORD,
                                        NOT_IN, NOT_IS, AS_SAFE,
 TYPEOF_KEYWORD
    );

 TokenSet SOFT_KEYWORDS = TokenSet.create(FILE_KEYWORD, IMPORT_KEYWORD, WHERE_KEYWORD, BY_KEYWORD, GET_KEYWORD,
                                             SET_KEYWORD, ABSTRACT_KEYWORD, ENUM_KEYWORD, OPEN_KEYWORD, INNER_KEYWORD,
                                             OVERRIDE_KEYWORD, PRIVATE_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD, PROTECTED_KEYWORD,
                                             CATCH_KEYWORD, FINALLY_KEYWORD, OUT_KEYWORD, FINAL_KEYWORD, VARARG_KEYWORD, REIFIED_KEYWORD,
                                             DYNAMIC_KEYWORD, COMPANION_KEYWORD, CONSTRUCTOR_KEYWORD, INIT_KEYWORD, SEALED_KEYWORD,
                                             FIELD_KEYWORD, PROPERTY_KEYWORD, RECEIVER_KEYWORD, PARAM_KEYWORD, SETPARAM_KEYWORD,
                                             DELEGATE_KEYWORD,
                                             LATEINIT_KEYWORD,
                                             DATA_KEYWORD, INLINE_KEYWORD, NOINLINE_KEYWORD, TAILREC_KEYWORD, EXTERNAL_KEYWORD,
                                             ANNOTATION_KEYWORD, CROSSINLINE_KEYWORD, CONST_KEYWORD, OPERATOR_KEYWORD, INFIX_KEYWORD,
                                             SUSPEND_KEYWORD, HEADER_KEYWORD, IMPL_KEYWORD, EXPECT_KEYWORD, ACTUAL_KEYWORD
    );

 /*
        This array is used in stub serialization:
        1. Do not change order.
        2. If you add an entry or change order, increase stub version.
     */
 KtModifierKeywordToken[] MODIFIER_KEYWORDS_ARRAY =
 new KtModifierKeywordToken[] {
                    ABSTRACT_KEYWORD, ENUM_KEYWORD, OPEN_KEYWORD, INNER_KEYWORD, OVERRIDE_KEYWORD, PRIVATE_KEYWORD,
                    PUBLIC_KEYWORD, INTERNAL_KEYWORD, PROTECTED_KEYWORD, OUT_KEYWORD, IN_KEYWORD, FINAL_KEYWORD, VARARG_KEYWORD,
                    REIFIED_KEYWORD, COMPANION_KEYWORD, SEALED_KEYWORD, LATEINIT_KEYWORD,
                    DATA_KEYWORD, INLINE_KEYWORD, NOINLINE_KEYWORD, TAILREC_KEYWORD, EXTERNAL_KEYWORD, ANNOTATION_KEYWORD, CROSSINLINE_KEYWORD,
                    CONST_KEYWORD, OPERATOR_KEYWORD, INFIX_KEYWORD, SUSPEND_KEYWORD,
                    HEADER_KEYWORD, IMPL_KEYWORD, EXPECT_KEYWORD, ACTUAL_KEYWORD
            };

 TokenSet MODIFIER_KEYWORDS = TokenSet.create(MODIFIER_KEYWORDS_ARRAY);

 TokenSet TYPE_MODIFIER_KEYWORDS = TokenSet.create(SUSPEND_KEYWORD);
 TokenSet TYPE_ARGUMENT_MODIFIER_KEYWORDS = TokenSet.create(IN_KEYWORD, OUT_KEYWORD);
 TokenSet RESERVED_VALUE_PARAMETER_MODIFIER_KEYWORDS = TokenSet.create(OUT_KEYWORD, VARARG_KEYWORD);

 TokenSet VISIBILITY_MODIFIERS = TokenSet.create(PRIVATE_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD, PROTECTED_KEYWORD);
 TokenSet MODALITY_MODIFIERS = TokenSet.create(ABSTRACT_KEYWORD, FINAL_KEYWORD, SEALED_KEYWORD, OPEN_KEYWORD);

 TokenSet WHITESPACES = TokenSet.create(TokenType.WHITE_SPACE);

    /**
     * Don't add KDocTokens to COMMENTS TokenSet, because it is used in KotlinParserDefinition.getCommentTokens(),
     * and therefor all COMMENTS tokens will be ignored by PsiBuilder.
     *
     * @see KtPsiUtil#isInComment(com.intellij.psi.PsiElement)
     */
 TokenSet COMMENTS = TokenSet.create(EOL_COMMENT, BLOCK_COMMENT, DOC_COMMENT, SHEBANG_COMMENT);
 TokenSet WHITE_SPACE_OR_COMMENT_BIT_SET = TokenSet.orSet(COMMENTS, WHITESPACES);

 TokenSet STRINGS = TokenSet.create(CHARACTER_LITERAL, REGULAR_STRING_PART);
 TokenSet OPERATIONS = TokenSet.create(AS_KEYWORD, AS_SAFE, IS_KEYWORD, IN_KEYWORD, DOT, PLUSPLUS, MINUSMINUS, EXCLEXCL, MUL, PLUS,
                                          MINUS, EXCL, DIV, PERC, LT, GT, LTEQ, GTEQ, EQEQEQ, EXCLEQEQEQ, EQEQ, EXCLEQ, ANDAND, OROR,
                                          SAFE_ACCESS, ELVIS,
                                          RANGE, EQ, MULTEQ, DIVEQ, PERCEQ, PLUSEQ, MINUSEQ,
                                          NOT_IN, NOT_IS,
                                          IDENTIFIER);

 TokenSet AUGMENTED_ASSIGNMENTS = TokenSet.create(PLUSEQ, MINUSEQ, MULTEQ, PERCEQ, DIVEQ);
 TokenSet ALL_ASSIGNMENTS = TokenSet.create(EQ, PLUSEQ, MINUSEQ, MULTEQ, PERCEQ, DIVEQ);
}

阅读完我们会发现Tokon进行了严格的分类,有关键字、运算符、标识符、修饰符、访问权限修饰符和操作符等等,相当详细;其实这个步骤就是将所有的Kotlin词法单元一一枚举出来并分组以后,再进行词法分析。

单纯从上面的分析我们无法知道Kotlin采用了什么词法分析器,看下面文件红色圈选:

可以知道其实它使用的是JFlex作为词法分析器,当然有现成的库,确实没必要自己造轮子,何况自己造轮子的技术也不一定好到哪里去。展开这个目录我们会发现很多关键的信息,如下:

Kotlin.jflex

配置文件分为三个部分:

用户代码 
%% 
选项与声明 (用来定制词法分析器,包括类名、父类、权限修饰符等等,以%开头作为标记)
%% 
词法规则(包括一组正则表达式和动作行为,也就是当正则表达式匹配成功后要执行的代码)

词法分析器

在上面目录下我们可以通过命名就能轻松的找到 KotlinLexer.java 文件,打开内容如下:

package org.jetbrains.kotlin.lexer;
import com.intellij.lexer.FlexAdapter;
import java.io.Reader;
public class KotlinLexer extends FlexAdapter {
 public KotlinLexer() {
 super(new _JetLexer((Reader) null));
    }
}

我们会发现实际上真正的类是 _JetLexer,我们在同目录结构下找到_JetLexer.java文件展开,阅读会发现 JFlex 会读取配置文件并生成一个词法分析器(扫描器),当对输入流进行词法分析时,词法分析器依据最长匹配规则来选择输入流的正规式,即所选择的正规式能最长的匹配当前输入流。如果同时有多个满足最长匹配的正规式,则生成的词法分析器将从中选择最先出现在词法规则描述中的正规式。在确定了起作用的正规式之后,将执行贵正规式所关联的动作。如果没有匹配的正规式,词法分析器将终止对输入流的分析并给出错误消息。

语法分析

语法分析的过程是建立在词法分析的基础上,将单词(Token)序列组合成各类语法短语,如“程序”,“语句”,“表达式”等等,语法分析器将判断源程序在结构上是否正确。在语法分析过程中,会生成语法树(ST)/ 抽象语法树(AST)。关于ST和AST的结构体,可以参阅

我们主要关注一下 KotlinParser,如下:

public class KotlinParser implements PsiParser {
 public KotlinParser(Project project) {
 }

 @Override
 @NotNull
 public ASTNode parse(@NotNull IElementType iElementType, @NotNull PsiBuilder psiBuilder) {
   throw new IllegalStateException("use another parse");
 }

 // PSI 程序结构接口,定义了程序的结构
 // we need this method because we need psiFile
 // psiFile 能够将源代码文件内容表示为特定编程语言元素的层次结构。说的通俗一点,PSI文件可以把Java、XML等语言代码表示为层次结构(树)的形式。
 // 例如,在IntelliJ开源的项目来看,PsiJavaFile可表示为Java文件,XmlFile表示为XML文件。通过PSI文件,我们能够遍历迭代文件中的元素,
 // 从而生成AST,正也正是语法分析中所需要的。
 // 语法分析生成AST树
 @NotNull
 public ASTNode parse(IElementType iElementType, PsiBuilder psiBuilder, PsiFile psiFile) {
   KotlinParsing ktParsing = KotlinParsing.createForTopLevel(new SemanticWhitespaceAwarePsiBuilderImpl(psiBuilder));
   String extension = FileUtilRt.getExtension(psiFile.getName());
   if (extension.isEmpty() || extension.equals(KotlinFileType.EXTENSION) || (psiFile instanceof KtFile && ((KtFile) psiFile).isCompiled())) {
     ktParsing.parseFile();
   } else {
     ktParsing.parseScript();
   }
     return psiBuilder.getTreeBuilt();
   }

 @NotNull
 public static ASTNode parseTypeCodeFragment(PsiBuilder psiBuilder) { // 类型分析
   KotlinParsing ktParsing = KotlinParsing.createForTopLevel(new SemanticWhitespaceAwarePsiBuilderImpl(psiBuilder));
   ktParsing.parseTypeCodeFragment();
   return psiBuilder.getTreeBuilt();
 }

 @NotNull
 public static ASTNode parseExpressionCodeFragment(PsiBuilder psiBuilder) { // 分析表达式分析
   KotlinParsing ktParsing = KotlinParsing.createForTopLevel(new SemanticWhitespaceAwarePsiBuilderImpl(psiBuilder));
   ktParsing.parseExpressionCodeFragment();
   return psiBuilder.getTreeBuilt();
 }

 @NotNull
 public static ASTNode parseBlockCodeFragment(PsiBuilder psiBuilder) { // 代码块分析
   KotlinParsing ktParsing = KotlinParsing.createForTopLevel(new SemanticWhitespaceAwarePsiBuilderImpl(psiBuilder));
   ktParsing.parseBlockCodeFragment();
   return psiBuilder.getTreeBuilt();
 }

 @NotNull
 public static ASTNode parseLambdaExpression(PsiBuilder psiBuilder) { // Lambda 表达式分析
   KotlinParsing ktParsing = KotlinParsing.createForTopLevel(new SemanticWhitespaceAwarePsiBuilderImpl(psiBuilder));
   ktParsing.parseLambdaExpression();
   return psiBuilder.getTreeBuilt();
 }

 @NotNull
 public static ASTNode parseBlockExpression(PsiBuilder psiBuilder) { // Block 表达式分析
   KotlinParsing ktParsing = KotlinParsing.createForTopLevel(new SemanticWhitespaceAwarePsiBuilderImpl(psiBuilder));
   ktParsing.parseBlockExpression();
   return psiBuilder.getTreeBuilt();
 }
}

KotlinParser 语法分析器调用 KotlinParsing 进行语法分析,并生成AST抽象语法树。关于如何生成一个简单表达式的AST树,可以参考下图:

语义分析及中间代码生成

语义分析的任务是检查抽象语法树AST的上下文相关属性,即检查源代码是否符合该编程语言的规范,比如变量类型定义是否正确,运算符是否匹配等等。举个栗子:

int iValue = 1;
string sValue = "hello";
float fValue = 0.4;
fValue = iValue * sValue;

上面的代码在语法分析阶段是符合语言编程结构的,但是在编译的时候却会失败的,是因为iValue、sValue、fValue类型各不相同且不能相互转换,不能进行运算操作,那么这个检测就是语义分析所要做的事情。

语义分析

语义分析的任务是对结构上正确的源代码进行上下文有关性质的审查,进行类型审查。语义分析是审查源代码有无语义错误,为代码生成阶段收集类型信息。Kotlin 语义分析模块在如下位置,它主要是包含了所有的的上下文相关属性的检查,包括对表达式语句、常量、智能转换等上下文相关属性检查。

中间代码生成

中间代码生成其实就很简单了,直接在编译模块寻找IL(Intermediate Language 中间语言)或者CLI(Common Intermediate Language 通用中间语言),Kotlin中我们寻找的是IR(intermediate representatio)中介码,如下:

如图绿色框选的位置所示,Psi2IrTranslator 文件是将 AST 抽象语法树转换成了 IR (即中介码)。代码如下:

class Psi2IrTranslator(
  val languageVersionSettings: LanguageVersionSettings,
  val configuration: Psi2IrConfiguration = Psi2IrConfiguration(),
  val facadeClassGenerator: (DeserializedContainerSource) -> IrClass? = { null }
) {
  interface PostprocessingStep {
  fun postprocess(context: GeneratorContext, irElement: IrElement)
}

private val postprocessingSteps = SmartList<PostprocessingStep>()
  fun add(step: PostprocessingStep) {
     postprocessingSteps.add(step)
  }

fun generateModule(
 moduleDescriptor: ModuleDescriptor,
 ktFiles: Collection<KtFile>,
 bindingContext: BindingContext,
 generatorExtensions: GeneratorExtensions
    ): IrModuleFragment {
    val context = createGeneratorContext(moduleDescriptor, bindingContext, extensions = generatorExtensions)
    return generateModuleFragment(context, ktFiles)
 }

fun createGeneratorContext(
  moduleDescriptor: ModuleDescriptor,
  bindingContext: BindingContext,
  symbolTable: SymbolTable = SymbolTable(),
  extensions: GeneratorExtensions = GeneratorExtensions()
    ): GeneratorContext =
  GeneratorContext(configuration, moduleDescriptor, bindingContext, languageVersionSettings, symbolTable, extensions)

fun generateModuleFragment(
  context: GeneratorContext,
  ktFiles: Collection<KtFile>,
  deserializer: IrDeserializer? = null
    ): IrModuleFragment {
    val moduleGenerator = ModuleGenerator(context)
    val irModule = moduleGenerator.generateModuleFragmentWithoutDependencies(ktFiles)

    // This is required for implicit casts insertion on IrTypes (work-in-progress).
    moduleGenerator.generateUnboundSymbolsAsDependencies(irModule, deserializer, facadeClassGenerator)
    irModule.patchDeclarationParents()
   
    postprocess(context, irModule)
    moduleGenerator.generateUnboundSymbolsAsDependencies(irModule, deserializer, facadeClassGenerator)
    return irModule
 }

private fun postprocess(context: GeneratorContext, irElement: IrElement) {
        insertImplicitCasts(irElement, context)
        generateAnnotationsForDeclarations(context, irElement)
        postprocessingSteps.forEach { it.postprocess(context, irElement) }
        irElement.patchDeclarationParents()
 }

private fun generateAnnotationsForDeclarations(context: GeneratorContext, irElement: IrElement) {
   val annotationGenerator = AnnotationGenerator(context)
   irElement.acceptVoid(annotationGenerator)
}
}

目标代码生成

其实这个阶段也比较好找,因为它一定属于编译后端,我们只需要在编译后端去寻找就行了,最终我们找到了我们想要的,如下图绿色圈选:

展开codegen文件夹我们找到 KotlinCodegenFacade.java 文件如下:

我们发现内部核心函数调用的是 CodegenFactory 类的 generateModule 函数:

public static void doGenerateFiles(
            @NotNull Collection<KtFile> files,
            @NotNull GenerationState state,
            @NotNull CompilationErrorHandler errorHandler
    ) {
          state.getCodegenFactory().generateModule(state, files, errorHandler);
          CodegenFactory.Companion.doCheckCancelled(state);
          state.getFactory().done();
}

于是我们在同目录找到了 CodegenFactory.kt 文件,打开文件观察如下:

核心代码如下:

// 目标代码类生成 
generateMultifileClass(state, multifileClassFqName, filesInMultifileClasses.get(multifileClassFqName), errorHandler)
...
// 目标代码包生成
 generatePackage(state, packageFqName, filesInPackages.get(packageFqName), errorHandler)

如果想继续想下查看,可以进一步在codegen目录下查询你想了解的代码生成过程,例如:ExpressionCodegen.java PropertyCodegen.java 等等。

PropertyCodegen

核心代码如下:

generateGetter(descriptor, getter); // 生成Getter
...
generateSetter(descriptor, setter);  // 生成Setter

可以看到,Kotlin 在目标代码生成环节做了更多的处理,在该环节实现了自动生成Getter、Setter的代码,这些语法糖还是很甜的;当然这也是和Java在编辑阶段最大的不同了。

总结

Kotlin 的整个编译过程磕磕绊绊的看了个大概,很多细节也没有深入研究,主要是时间有限,各种转需要搬,当然里面任何一个环节的内容也不是一篇文章就可以讲清楚的,例如词法分析,如果想表述清楚,估计每个几篇文章是很难讲清楚的。

编译前端(即词法分析、语法分析、语义分析、中间代码生成),和Java是基本上一某一样。编译后端也就是目标代码生成环节倒是有些不一样,增加了很多语法糖,它在目标代码生成环节做了很多类似于Java 代码封装的事情,例如自动生成Getter/Setter代码、Companion 转变成静态类、修改类属性为 final 不可继承等等。可以说,大部分 Kotlin 的特性都在这个环节处理产生,它将我们本来在代码层做的部分封装工作转移到了编译后端阶段,以使得我们可以更加简洁的使用这门语言。

发布于 2019-08-21